+
-
+
@@ -369,8 +369,8 @@ export default class PersonaEditor extends Component {
@@ -380,7 +380,7 @@ export default class PersonaEditor extends Component {
{{#if data.vision_enabled}}
@@ -408,8 +408,8 @@ export default class PersonaEditor extends Component {
{{#unless data.system}}
{{#unless data.system}}
{{/unless}}
{{#if (gt data.examples.length 0)}}
-
-
+
@@ -467,7 +467,7 @@ export default class PersonaEditor extends Component {
@value={{field.value}}
@disabled={{data.system}}
@onChange={{fn this.updateToolNames form data}}
- @content={{@personas.resultSetMeta.tools}}
+ @content={{@agents.resultSetMeta.tools}}
/>
@@ -475,7 +475,7 @@ export default class PersonaEditor extends Component {
{{#if (gt data.tools.length 0)}}
@@ -493,7 +493,7 @@ export default class PersonaEditor extends Component {
{{#if (gt data.forcedTools.length 0)}}
@@ -508,19 +508,19 @@ export default class PersonaEditor extends Component {
{{#if (gt data.tools.length 0)}}
-
{{/if}}
@@ -535,10 +535,10 @@ export default class PersonaEditor extends Component {
@@ -546,16 +546,16 @@ export default class PersonaEditor extends Component {
@@ -587,10 +587,10 @@ export default class PersonaEditor extends Component {
{{/if}}
-
+
@@ -599,21 +599,21 @@ export default class PersonaEditor extends Component {
{{#if @model.isNew}}
-
@@ -622,12 +622,12 @@ export default class PersonaEditor extends Component {
{{/if}}
{{#if data.user}}
{{/if}}
{{#if data.user}}
{{/unless}}
diff --git a/assets/javascripts/discourse/components/ai-persona-example.gjs b/assets/javascripts/discourse/components/ai-agent-example.gjs
similarity index 82%
rename from assets/javascripts/discourse/components/ai-persona-example.gjs
rename to assets/javascripts/discourse/components/ai-agent-example.gjs
index 3ee031145..7e44b008c 100644
--- a/assets/javascripts/discourse/components/ai-persona-example.gjs
+++ b/assets/javascripts/discourse/components/ai-agent-example.gjs
@@ -7,7 +7,7 @@ import { eq } from "truth-helpers";
import icon from "discourse/helpers/d-icon";
import { i18n } from "discourse-i18n";
-export default class AiPersonaCollapsableExample extends Component {
+export default class AiAgentCollapsableExample extends Component {
@tracked collapsed = true;
get caretIcon() {
@@ -26,7 +26,7 @@ export default class AiPersonaCollapsableExample extends Component {
}
get exampleTitle() {
- return i18n("discourse_ai.ai_persona.examples.collapsable_title", {
+ return i18n("discourse_ai.ai_agent.examples.collapsable_title", {
number: this.args.exampleNumber + 1,
});
}
@@ -41,7 +41,7 @@ export default class AiPersonaCollapsableExample extends Component {
<@form.Button
@action={{this.deletePair}}
- @label="discourse_ai.ai_persona.examples.remove"
- class="ai-persona-editor__delete_example btn-danger"
+ @label="discourse_ai.ai_agent.examples.remove"
+ class="ai-agent-editor__delete_example btn-danger"
/>
@form.Container>
{{/unless}}
diff --git a/assets/javascripts/discourse/components/ai-agent-list-editor.gjs b/assets/javascripts/discourse/components/ai-agent-list-editor.gjs
new file mode 100644
index 000000000..e0ba1c1bc
--- /dev/null
+++ b/assets/javascripts/discourse/components/ai-agent-list-editor.gjs
@@ -0,0 +1,117 @@
+import Component from "@glimmer/component";
+import { fn } from "@ember/helper";
+import { on } from "@ember/modifier";
+import { action } from "@ember/object";
+import { LinkTo } from "@ember/routing";
+import { service } from "@ember/service";
+import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
+import DPageSubheader from "discourse/components/d-page-subheader";
+import DToggleSwitch from "discourse/components/d-toggle-switch";
+import concatClass from "discourse/helpers/concat-class";
+import { popupAjaxError } from "discourse/lib/ajax-error";
+import { i18n } from "discourse-i18n";
+import AdminConfigAreaEmptyList from "admin/components/admin-config-area-empty-list";
+import AiAgentEditor from "./ai-agent-editor";
+
+export default class AiAgentListEditor extends Component {
+ @service adminPluginNavManager;
+
+ @action
+ async toggleEnabled(agent) {
+ const oldValue = agent.enabled;
+ const newValue = !oldValue;
+
+ try {
+ agent.set("enabled", newValue);
+ await agent.save();
+ } catch (err) {
+ agent.set("enabled", oldValue);
+ popupAjaxError(err);
+ }
+ }
+
+
+
+
+ {{#if @currentAgent}}
+
+ {{else}}
+
+ <:actions as |actions|>
+
+
+
+
+ {{#if @agents}}
+
+
+
+ {{else}}
+
+ {{/if}}
+ {{/if}}
+
+
+}
diff --git a/assets/javascripts/discourse/components/ai-persona-llm-selector.gjs b/assets/javascripts/discourse/components/ai-agent-llm-selector.gjs
similarity index 67%
rename from assets/javascripts/discourse/components/ai-persona-llm-selector.gjs
rename to assets/javascripts/discourse/components/ai-agent-llm-selector.gjs
index aa50ae603..6f40dd53c 100644
--- a/assets/javascripts/discourse/components/ai-persona-llm-selector.gjs
+++ b/assets/javascripts/discourse/components/ai-agent-llm-selector.gjs
@@ -6,10 +6,10 @@ import { service } from "@ember/service";
import { i18n } from "discourse-i18n";
import DropdownSelectBox from "select-kit/components/dropdown-select-box";
-const PERSONA_SELECTOR_KEY = "ai_persona_selector_id";
+const AGENT_SELECTOR_KEY = "ai_agent_selector_id";
const LLM_SELECTOR_KEY = "ai_llm_selector_id";
-export default class AiPersonaLlmSelector extends Component {
+export default class AiAgentLlmSelector extends Component {
@service currentUser;
@service keyValueStore;
@@ -20,7 +20,7 @@ export default class AiPersonaLlmSelector extends Component {
super(...arguments);
if (this.botOptions?.length) {
- this.#loadStoredPersona();
+ this.#loadStoredAgent();
this.#loadStoredLlm();
next(() => {
@@ -34,25 +34,25 @@ export default class AiPersonaLlmSelector extends Component {
}
get hasLlmSelector() {
- return this.currentUser.ai_enabled_chat_bots.any((bot) => !bot.is_persona);
+ return this.currentUser.ai_enabled_chat_bots.any((bot) => !bot.is_agent);
}
get botOptions() {
- if (!this.currentUser.ai_enabled_personas) {
+ if (!this.currentUser.ai_enabled_agents) {
return;
}
- let enabledPersonas = this.currentUser.ai_enabled_personas;
+ let enabledAgents = this.currentUser.ai_enabled_agents;
if (!this.hasLlmSelector) {
- enabledPersonas = enabledPersonas.filter((persona) => persona.username);
+ enabledAgents = enabledAgents.filter((agent) => agent.username);
}
- return enabledPersonas.map((persona) => {
+ return enabledAgents.map((agent) => {
return {
- id: persona.id,
- name: persona.name,
- description: persona.description,
+ id: agent.id,
+ name: agent.name,
+ description: agent.description,
};
});
}
@@ -67,8 +67,8 @@ export default class AiPersonaLlmSelector extends Component {
set value(newValue) {
this._value = newValue;
- this.keyValueStore.setItem(PERSONA_SELECTOR_KEY, newValue);
- this.args.setPersonaId(newValue);
+ this.keyValueStore.setItem(AGENT_SELECTOR_KEY, newValue);
+ this.args.setAgentId(newValue);
this.setAllowLLMSelector();
this.resetTargetRecipients();
}
@@ -79,11 +79,11 @@ export default class AiPersonaLlmSelector extends Component {
return;
}
- const persona = this.currentUser.ai_enabled_personas.find(
- (innerPersona) => innerPersona.id === this._value
+ const agent = this.currentUser.ai_enabled_agents.find(
+ (innerAgent) => innerAgent.id === this._value
);
- this.allowLLMSelector = !persona?.force_default_llm;
+ this.allowLLMSelector = !agent?.force_default_llm;
}
get currentLlm() {
@@ -104,16 +104,16 @@ export default class AiPersonaLlmSelector extends Component {
).username;
this.args.setTargetRecipient(botUsername);
} else {
- const persona = this.currentUser.ai_enabled_personas.find(
- (innerPersona) => innerPersona.id === this._value
+ const agent = this.currentUser.ai_enabled_agents.find(
+ (innerAgent) => innerAgent.id === this._value
);
- this.args.setTargetRecipient(persona.username || "");
+ this.args.setTargetRecipient(agent.username || "");
}
}
get llmOptions() {
const availableBots = this.currentUser.ai_enabled_chat_bots
- .filter((bot) => !bot.is_persona)
+ .filter((bot) => !bot.is_agent)
.filter(Boolean);
return availableBots
@@ -130,18 +130,18 @@ export default class AiPersonaLlmSelector extends Component {
return this.allowLLMSelector && this.llmOptions.length > 1;
}
- #loadStoredPersona() {
- let personaId = this.keyValueStore.getItem(PERSONA_SELECTOR_KEY);
+ #loadStoredAgent() {
+ let agentId = this.keyValueStore.getItem(AGENT_SELECTOR_KEY);
this._value = this.botOptions[0].id;
- if (personaId) {
- personaId = parseInt(personaId, 10);
- if (this.botOptions.any((bot) => bot.id === personaId)) {
- this._value = personaId;
+ if (agentId) {
+ agentId = parseInt(agentId, 10);
+ if (this.botOptions.any((bot) => bot.id === agentId)) {
+ this._value = agentId;
}
}
- this.args.setPersonaId(this._value);
+ this.args.setAgentId(this._value);
}
#loadStoredLlm() {
@@ -172,13 +172,13 @@ export default class AiPersonaLlmSelector extends Component {
}
-
{{i18n "discourse_ai.ai_persona.ai_bot.save_first"}}
+ {{i18n "discourse_ai.ai_agent.ai_bot.save_first"}}
{{else}}
{{#if data.default_llm_id}}
{{i18n "discourse_ai.ai_agent.name"}} | +{{i18n "discourse_ai.ai_agent.list.enabled"}} | ++ |
---|---|---|
+
+
+
+
+ {{agent.name}}
+
+
+
+ {{agent.description}}
+
+ |
+
+ |
+
+ |
+
-
+
+
{{#if @showLabels}}
-
+
{{/if}}
{{#if this.showLLMSelector}}
-
+
{{#if @showLabels}}
{{/if}}
{{#if this.showToolOptions}}
<@form.Container
- @title={{i18n "discourse_ai.ai_persona.tool_options"}}
+ @title={{i18n "discourse_ai.ai_agent.tool_options"}}
@direction="column"
@format="full"
>
<@form.Object
@name="toolOptions"
- @title={{i18n "discourse_ai.ai_persona.tool_options"}}
+ @title={{i18n "discourse_ai.ai_agent.tool_options"}}
as |toolObj optsPerTool|
>
{{#each (this.formObjectKeys optsPerTool) as |toolId|}}
-
+
{{#let (get this.toolsMetadata toolId) as |toolMeta|}}
-
+
@@ -73,7 +73,7 @@ export default class AiPersonaToolOptions extends Component {
@value={{field.value}}
@llms={{@llms}}
@onChange={{field.set}}
- @class="ai-persona-tool-option-editor__llms"
+ @class="ai-agent-tool-option-editor__llms"
/>
{{else if (eq optionMeta.type "boolean")}}
diff --git a/assets/javascripts/discourse/components/ai-bot-conversations.gjs b/assets/javascripts/discourse/components/ai-bot-conversations.gjs
index 9e86d3f04..a17fed968 100644
--- a/assets/javascripts/discourse/components/ai-bot-conversations.gjs
+++ b/assets/javascripts/discourse/components/ai-bot-conversations.gjs
@@ -23,7 +23,7 @@ import {
} from "discourse/lib/user-status-on-autocomplete";
import { clipboardHelpers } from "discourse/lib/utilities";
import { i18n } from "discourse-i18n";
-import AiPersonaLlmSelector from "discourse/plugins/discourse-ai/discourse/components/ai-persona-llm-selector";
+import AiAgentLlmSelector from "discourse/plugins/discourse-ai/discourse/components/ai-agent-llm-selector";
export default class AiBotConversations extends Component {
@service aiBotConversationsHiddenSubmit;
@@ -133,8 +133,8 @@ export default class AiBotConversations extends Component {
}
@action
- setPersonaId(id) {
- this.aiBotConversationsHiddenSubmit.personaId = id;
+ setAgentId(id) {
+ this.aiBotConversationsHiddenSubmit.agentId = id;
}
@action
@@ -279,9 +279,9 @@ export default class AiBotConversations extends Component {
{{toolMeta.name}}
-
diff --git a/assets/javascripts/discourse/components/ai-llm-editor-form.gjs b/assets/javascripts/discourse/components/ai-llm-editor-form.gjs
index 38cf1b50f..682763658 100644
--- a/assets/javascripts/discourse/components/ai-llm-editor-form.gjs
+++ b/assets/javascripts/discourse/components/ai-llm-editor-form.gjs
@@ -116,7 +116,7 @@ export default class AiLlmEditorForm extends Component {
const localized = usedBy.map((m) => {
return i18n(`discourse_ai.llms.usage.${m.type}`, {
- persona: m.name,
+ agent: m.name,
});
});
diff --git a/assets/javascripts/discourse/components/ai-llm-selector.gjs b/assets/javascripts/discourse/components/ai-llm-selector.gjs
index f5d9d95e4..e5feef9a8 100644
--- a/assets/javascripts/discourse/components/ai-llm-selector.gjs
+++ b/assets/javascripts/discourse/components/ai-llm-selector.gjs
@@ -8,7 +8,7 @@ const AiLlmSelector =
@onChange={{@onChange}}
@options={{hash
filterable=true
- none="discourse_ai.ai_persona.no_llm_selected"
+ none="discourse_ai.ai_agent.no_llm_selected"
}}
class={{@class}}
/>
diff --git a/assets/javascripts/discourse/components/ai-llms-list-editor.gjs b/assets/javascripts/discourse/components/ai-llms-list-editor.gjs
index 5413a3a36..1f168eb72 100644
--- a/assets/javascripts/discourse/components/ai-llms-list-editor.gjs
+++ b/assets/javascripts/discourse/components/ai-llms-list-editor.gjs
@@ -112,9 +112,9 @@ export default class AiLlmsListEditor extends Component {
}
localizeUsage(usage) {
- if (usage.type === "ai_persona") {
- return i18n("discourse_ai.llms.usage.ai_persona", {
- persona: usage.name,
+ if (usage.type === "ai_agent") {
+ return i18n("discourse_ai.llms.usage.ai_agent", {
+ agent: usage.name,
});
} else if (usage.type === "automation") {
return i18n("discourse_ai.llms.usage.automation", {
diff --git a/assets/javascripts/discourse/components/ai-persona-list-editor.gjs b/assets/javascripts/discourse/components/ai-persona-list-editor.gjs
deleted file mode 100644
index 69cadec15..000000000
--- a/assets/javascripts/discourse/components/ai-persona-list-editor.gjs
+++ /dev/null
@@ -1,117 +0,0 @@
-import Component from "@glimmer/component";
-import { fn } from "@ember/helper";
-import { on } from "@ember/modifier";
-import { action } from "@ember/object";
-import { LinkTo } from "@ember/routing";
-import { service } from "@ember/service";
-import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
-import DPageSubheader from "discourse/components/d-page-subheader";
-import DToggleSwitch from "discourse/components/d-toggle-switch";
-import concatClass from "discourse/helpers/concat-class";
-import { popupAjaxError } from "discourse/lib/ajax-error";
-import { i18n } from "discourse-i18n";
-import AdminConfigAreaEmptyList from "admin/components/admin-config-area-empty-list";
-import AiPersonaEditor from "./ai-persona-editor";
-
-export default class AiPersonaListEditor extends Component {
- @service adminPluginNavManager;
-
- @action
- async toggleEnabled(persona) {
- const oldValue = persona.enabled;
- const newValue = !oldValue;
-
- try {
- persona.set("enabled", newValue);
- await persona.save();
- } catch (err) {
- persona.set("enabled", oldValue);
- popupAjaxError(err);
- }
- }
-
-
-
-
- {{#if @currentPersona}}
-
- {{else}}
-
- <:actions as |actions|>
-
-
-
-
- {{#if @personas}}
-
-
-
- {{else}}
-
- {{/if}}
- {{/if}}
-
-
-}
diff --git a/assets/javascripts/discourse/components/ai-search-discoveries.gjs b/assets/javascripts/discourse/components/ai-search-discoveries.gjs
index e3e2dcbdc..99f0c5468 100644
--- a/assets/javascripts/discourse/components/ai-search-discoveries.gjs
+++ b/assets/javascripts/discourse/components/ai-search-discoveries.gjs
@@ -154,8 +154,8 @@ export default class AiSearchDiscoveries extends Component {
}
get canContinueConversation() {
- const personas = this.currentUser?.ai_enabled_personas;
- if (!personas) {
+ const agents = this.currentUser?.ai_enabled_agents;
+ if (!agents) {
return false;
}
@@ -163,16 +163,16 @@ export default class AiSearchDiscoveries extends Component {
return false;
}
- const discoverPersona = personas.find(
- (persona) =>
- persona.id === parseInt(this.siteSettings?.ai_bot_discover_persona, 10)
+ const discoverAgent = agents.find(
+ (agent) =>
+ agent.id === parseInt(this.siteSettings?.ai_bot_discover_agent, 10)
);
- const discoverPersonaHasBot = discoverPersona?.username;
+ const discoverAgentHasBot = discoverAgent?.username;
return (
this.discobotDiscoveries.discovery?.length > 0 &&
!this.smoothStreamer.isStreaming &&
- discoverPersonaHasBot
+ discoverAgentHasBot
);
}
diff --git a/assets/javascripts/discourse/components/modal/ai-persona-response-format-editor.gjs b/assets/javascripts/discourse/components/modal/ai-agent-response-format-editor.gjs
similarity index 76%
rename from assets/javascripts/discourse/components/modal/ai-persona-response-format-editor.gjs
rename to assets/javascripts/discourse/components/modal/ai-agent-response-format-editor.gjs
index 3f9876986..960284812 100644
--- a/assets/javascripts/discourse/components/modal/ai-persona-response-format-editor.gjs
+++ b/assets/javascripts/discourse/components/modal/ai-agent-response-format-editor.gjs
@@ -7,16 +7,16 @@ import ModalJsonSchemaEditor from "discourse/components/modal/json-schema-editor
import { prettyJSON } from "discourse/lib/formatter";
import { i18n } from "discourse-i18n";
-export default class AiPersonaResponseFormatEditor extends Component {
+export default class AiAgentResponseFormatEditor extends Component {
@tracked showJsonEditorModal = false;
jsonSchema = {
type: "array",
uniqueItems: true,
- title: i18n("discourse_ai.ai_persona.response_format.modal.root_title"),
+ title: i18n("discourse_ai.ai_agent.response_format.modal.root_title"),
items: {
type: "object",
- title: i18n("discourse_ai.ai_persona.response_format.modal.key_title"),
+ title: i18n("discourse_ai.ai_agent.response_format.modal.key_title"),
properties: {
key: {
type: "string",
@@ -30,7 +30,7 @@ export default class AiPersonaResponseFormatEditor extends Component {
};
get editorTitle() {
- return i18n("discourse_ai.ai_persona.response_format.title");
+ return i18n("discourse_ai.ai_agent.response_format.title");
}
get responseFormatAsJSON() {
@@ -64,21 +64,21 @@ export default class AiPersonaResponseFormatEditor extends Component {
<@form.Container @title={{this.editorTitle}} @format="large">
-
{{i18n "discourse_ai.ai_persona.name"}} | -{{i18n "discourse_ai.ai_persona.list.enabled"}} | -- |
---|---|---|
-
-
-
-
- {{persona.name}}
-
-
-
- {{persona.description}}
-
- |
-
- |
-
- |
-
+
{{#if (gt @data.response_format.length 0)}}
-
+{{else}} -{{this.displayJSON}}
- {{i18n "discourse_ai.ai_persona.response_format.no_format"}} +diff --git a/assets/javascripts/discourse/components/post/ai-persona-flair.gjs b/assets/javascripts/discourse/components/post/ai-agent-flair.gjs similarity index 60% rename from assets/javascripts/discourse/components/post/ai-persona-flair.gjs rename to assets/javascripts/discourse/components/post/ai-agent-flair.gjs index af6371590..44a596c60 100644 --- a/assets/javascripts/discourse/components/post/ai-persona-flair.gjs +++ b/assets/javascripts/discourse/components/post/ai-agent-flair.gjs @@ -1,14 +1,14 @@ import Component from "@glimmer/component"; import { isGPTBot } from "../../lib/ai-bot-helper"; -export default class AiPersonaFlair extends Component { +export default class AiAgentFlair extends Component { static shouldRender(args) { return isGPTBot(args.post.user); } - - {{@outletArgs.post.topic.ai_persona_name}} + + {{@outletArgs.post.topic.ai_agent_name}} } diff --git a/assets/javascripts/discourse/components/rag-options-fk.gjs b/assets/javascripts/discourse/components/rag-options-fk.gjs index e540f7441..0117d1a2c 100644 --- a/assets/javascripts/discourse/components/rag-options-fk.gjs +++ b/assets/javascripts/discourse/components/rag-options-fk.gjs @@ -70,7 +70,7 @@ export default class RagOptionsFk extends Component { @value={{field.value}} @llms={{this.visionLlms}} @onChange={{field.set}} - @class="ai-persona-editor__llms" + @class="ai-agent-editor__llms" /> @form.Field> diff --git a/assets/javascripts/discourse/connectors/composer-fields/persona-llm-selector.gjs b/assets/javascripts/discourse/connectors/composer-fields/agent-llm-selector.gjs similarity index 73% rename from assets/javascripts/discourse/connectors/composer-fields/persona-llm-selector.gjs rename to assets/javascripts/discourse/connectors/composer-fields/agent-llm-selector.gjs index 3eaaaf71a..d3190aa70 100644 --- a/assets/javascripts/discourse/connectors/composer-fields/persona-llm-selector.gjs +++ b/assets/javascripts/discourse/connectors/composer-fields/agent-llm-selector.gjs @@ -1,7 +1,7 @@ import Component from "@glimmer/component"; import { action } from "@ember/object"; import { service } from "@ember/service"; -import AiPersonaLlmSelector from "discourse/plugins/discourse-ai/discourse/components/ai-persona-llm-selector"; +import AiAgentLlmSelector from "discourse/plugins/discourse-ai/discourse/components/ai-agent-llm-selector"; function isBotMessage(composer, currentUser) { if ( @@ -21,7 +21,7 @@ function isBotMessage(composer, currentUser) { export default class BotSelector extends Component { static shouldRender(args, container) { return ( - container?.currentUser?.ai_enabled_personas && + container?.currentUser?.ai_enabled_agents && isBotMessage(args.model, container.currentUser) ); } @@ -29,8 +29,8 @@ export default class BotSelector extends Component { @service currentUser; @action - setPersonaIdOnComposer(id) { - this.args.outletArgs.model.metaData = { ai_persona_id: id }; + setAgentIdOnComposer(id) { + this.args.outletArgs.model.metaData = { ai_agent_id: id }; } @action @@ -39,8 +39,8 @@ export default class BotSelector extends Component { } -+ {{i18n "discourse_ai.ai_agent.response_format.no_format"}}{{/if}} <@form.Button @action={{this.openModal}} - @label="discourse_ai.ai_persona.response_format.open_modal" + @label="discourse_ai.ai_agent.response_format.open_modal" @disabled={{@data.system}} />diff --git a/assets/javascripts/discourse/connectors/full-page-search-below-search-header/ai-full-page-discobot-discoveries.gjs b/assets/javascripts/discourse/connectors/full-page-search-below-search-header/ai-full-page-discobot-discoveries.gjs index 6662a6287..1506de9d8 100644 --- a/assets/javascripts/discourse/connectors/full-page-search-below-search-header/ai-full-page-discobot-discoveries.gjs +++ b/assets/javascripts/discourse/connectors/full-page-search-below-search-header/ai-full-page-discobot-discoveries.gjs @@ -9,8 +9,8 @@ import AiSearchDiscoveriesTooltip from "../../components/ai-search-discoveries-t export default class AiFullPageDiscobotDiscoveries extends Component { static shouldRender(_args, { siteSettings, currentUser }) { return ( - siteSettings.ai_bot_discover_persona && - currentUser?.can_use_ai_bot_discover_persona && + siteSettings.ai_bot_discover_agent && + currentUser?.can_use_ai_bot_discover_agent && currentUser?.user_option?.ai_search_discoveries ); } diff --git a/assets/javascripts/discourse/connectors/search-menu-results-top/ai-discobot-discoveries.gjs b/assets/javascripts/discourse/connectors/search-menu-results-top/ai-discobot-discoveries.gjs index 6256656fc..05631abf2 100644 --- a/assets/javascripts/discourse/connectors/search-menu-results-top/ai-discobot-discoveries.gjs +++ b/assets/javascripts/discourse/connectors/search-menu-results-top/ai-discobot-discoveries.gjs @@ -8,8 +8,8 @@ import AiSearchDiscoveriesTooltip from "../../components/ai-search-discoveries-t export default class AiDiscobotDiscoveries extends Component { static shouldRender(args, { siteSettings, currentUser }) { return ( - siteSettings.ai_bot_discover_persona && - currentUser?.can_use_ai_bot_discover_persona && + siteSettings.ai_bot_discover_agent && + currentUser?.can_use_ai_bot_discover_agent && currentUser?.user_option?.ai_search_discoveries ); } diff --git a/assets/javascripts/discourse/controllers/preferences-ai.js b/assets/javascripts/discourse/controllers/preferences-ai.js index adab3654c..e1bf6a011 100644 --- a/assets/javascripts/discourse/controllers/preferences-ai.js +++ b/assets/javascripts/discourse/controllers/preferences-ai.js @@ -35,8 +35,8 @@ export default class PreferencesAiController extends Controller { checked: this.model.user_option.ai_search_discoveries, isIncluded: (() => { return ( - this.siteSettings.ai_bot_discover_persona && - this.model?.can_use_ai_bot_discover_persona && + this.siteSettings.ai_bot_discover_agent && + this.model?.can_use_ai_bot_discover_agent && this.siteSettings.ai_bot_enabled ); })(), diff --git a/assets/javascripts/discourse/lib/ai-bot-helper.js b/assets/javascripts/discourse/lib/ai-bot-helper.js index 913f0bf95..91fce0f2a 100644 --- a/assets/javascripts/discourse/lib/ai-bot-helper.js +++ b/assets/javascripts/discourse/lib/ai-bot-helper.js @@ -5,7 +5,7 @@ import Composer from "discourse/models/composer"; import { i18n } from "discourse-i18n"; import ShareFullTopicModal from "../components/modal/share-full-topic-modal"; -const MAX_PERSONA_USER_ID = -1200; +const MAX_AGENT_USER_ID = -1200; let enabledChatBotMap = null; @@ -40,12 +40,12 @@ export function getBotType(user) { if (!bot) { return; } - return bot.is_persona ? "persona" : "llm"; + return bot.is_agent ? "agent" : "llm"; } export function isPostFromAiBot(post, currentUser) { return ( - post.user_id <= MAX_PERSONA_USER_ID || + post.user_id <= MAX_AGENT_USER_ID || !!currentUser?.ai_enabled_chat_bots?.any( (bot) => post.username === bot.username ) @@ -66,7 +66,7 @@ export async function composeAiBotMessage( options = { skipFocus: false, topicBody: "", - personaUsername: null, + agentUsername: null, } ) { const currentUser = composer.currentUser; @@ -77,8 +77,8 @@ export async function composeAiBotMessage( botUsername = currentUser.ai_enabled_chat_bots.find( (bot) => bot.model_name === targetBot )?.username; - } else if (options.personaUsername) { - botUsername = options.personaUsername; + } else if (options.agentUsername) { + botUsername = options.agentUsername; } else { botUsername = currentUser.ai_enabled_chat_bots[0].username; } diff --git a/assets/javascripts/discourse/services/ai-bot-conversations-hidden-submit.js b/assets/javascripts/discourse/services/ai-bot-conversations-hidden-submit.js index 1749ba98a..0dd99ad71 100644 --- a/assets/javascripts/discourse/services/ai-bot-conversations-hidden-submit.js +++ b/assets/javascripts/discourse/services/ai-bot-conversations-hidden-submit.js @@ -16,7 +16,7 @@ export default class AiBotConversationsHiddenSubmit extends Service { @tracked loading = false; - personaId; + agentId; targetUsername; uploads = []; @@ -35,12 +35,12 @@ export default class AiBotConversationsHiddenSubmit extends Service { async submitToBot() { if ( this.inputValue.length < - this.siteSettings.min_personal_message_post_length + this.siteSettings.min_agentl_message_post_length ) { return this.dialog.alert({ message: i18n( "discourse_ai.ai_bot.conversations.min_input_length_message", - { count: this.siteSettings.min_personal_message_post_length } + { count: this.siteSettings.min_agentl_message_post_length } ), didConfirm: () => this.focusInput(), didCancel: () => this.focusInput(), @@ -78,7 +78,7 @@ export default class AiBotConversationsHiddenSubmit extends Service { title, archetype: "private_message", target_recipients: this.targetUsername, - meta_data: { ai_persona_id: this.personaId }, + meta_data: { ai_agent_id: this.agentId }, }, }); diff --git a/assets/javascripts/initializers/admin-plugin-configuration-nav.js b/assets/javascripts/initializers/admin-plugin-configuration-nav.js index 391f5cbc7..c94c09668 100644 --- a/assets/javascripts/initializers/admin-plugin-configuration-nav.js +++ b/assets/javascripts/initializers/admin-plugin-configuration-nav.js @@ -22,9 +22,9 @@ export default { description: "discourse_ai.llms.preconfigured.description", }, { - label: "discourse_ai.ai_persona.short_title", - route: "adminPlugins.show.discourse-ai-personas", - description: "discourse_ai.ai_persona.persona_description", + label: "discourse_ai.ai_agent.short_title", + route: "adminPlugins.show.discourse-ai-agents", + description: "discourse_ai.ai_agent.agent_description", }, { label: "discourse_ai.embeddings.short_title", diff --git a/assets/javascripts/initializers/ai-bot-replies.js b/assets/javascripts/initializers/ai-bot-replies.js index 822e6ffd1..92ccf54d3 100644 --- a/assets/javascripts/initializers/ai-bot-replies.js +++ b/assets/javascripts/initializers/ai-bot-replies.js @@ -3,7 +3,7 @@ import { withSilencedDeprecations } from "discourse/lib/deprecated"; import { withPluginApi } from "discourse/lib/plugin-api"; import { registerWidgetShim } from "discourse/widgets/render-glimmer"; import AiBotHeaderIcon from "../discourse/components/ai-bot-header-icon"; -import AiPersonaFlair from "../discourse/components/post/ai-persona-flair"; +import AiAgentFlair from "../discourse/components/post/ai-agent-flair"; import AiCancelStreamingButton from "../discourse/components/post-menu/ai-cancel-streaming-button"; import AiDebugButton from "../discourse/components/post-menu/ai-debug-button"; import AiShareButton from "../discourse/components/post-menu/ai-share-button"; @@ -53,35 +53,35 @@ function initializeAIBotReplies(api) { }); } -function initializePersonaDecorator(api) { - api.renderAfterWrapperOutlet("post-meta-data-poster-name", AiPersonaFlair); +function initializeAgentDecorator(api) { + api.renderAfterWrapperOutlet("post-meta-data-poster-name", AiAgentFlair); withSilencedDeprecations("discourse.post-stream-widget-overrides", () => - initializeWidgetPersonaDecorator(api) + initializeWidgetAgentDecorator(api) ); } -function initializeWidgetPersonaDecorator(api) { +function initializeWidgetAgentDecorator(api) { api.decorateWidget(`poster-name:after`, (dec) => { const botType = getBotType(dec.attrs.user); // we have 2 ways of decorating - // 1. if a bot is a LLM we decorate with persona name - // 2. if bot is a persona we decorate with LLM name + // 1. if a bot is a LLM we decorate with agent name + // 2. if bot is a agent we decorate with LLM name if (botType === "llm") { - return dec.widget.attach("persona-flair", { - personaName: dec.model?.topic?.ai_persona_name, + return dec.widget.attach("agent-flair", { + agentName: dec.model?.topic?.ai_agent_name, }); - } else if (botType === "persona") { - return dec.widget.attach("persona-flair", { - personaName: dec.model?.llm_name, + } else if (botType === "agent") { + return dec.widget.attach("agent-flair", { + agentName: dec.model?.llm_name, }); } }); registerWidgetShim( - "persona-flair", - "span.persona-flair", - hbs`{{@data.personaName}}` + "agent-flair", + "span.agent-flair", + hbs`{{@data.agentName}}` ); } @@ -149,11 +149,11 @@ function initializeShareTopicButton(api) { showShareConversationModal(modal, this.topic.id); }, classNames: ["share-ai-conversation-button"], - dependentKeys: ["topic.ai_persona_name"], + dependentKeys: ["topic.ai_agent_name"], displayed() { return ( currentUser?.can_share_ai_bot_conversations && - this.topic.ai_persona_name + this.topic.ai_agent_name ); }, }); @@ -171,7 +171,7 @@ export default { withPluginApi((api) => { attachHeaderIcon(api); initializeAIBotReplies(api); - initializePersonaDecorator(api); + initializeAgentDecorator(api); initializeDebugButton(api, container); initializeShareButton(api, container); initializeShareTopicButton(api, container); diff --git a/assets/javascripts/initializers/ai-search-discoveries.js b/assets/javascripts/initializers/ai-search-discoveries.js index 339cb9441..d5f9434b5 100644 --- a/assets/javascripts/initializers/ai-search-discoveries.js +++ b/assets/javascripts/initializers/ai-search-discoveries.js @@ -6,7 +6,7 @@ export default apiInitializer((api) => { if ( !settings.ai_bot_enabled || - !currentUser?.can_use_ai_bot_discover_persona + !currentUser?.can_use_ai_bot_discover_agent ) { return; } diff --git a/assets/stylesheets/common/ai-features.scss b/assets/stylesheets/common/ai-features.scss index 6a29541cb..05ccd103d 100644 --- a/assets/stylesheets/common/ai-features.scss +++ b/assets/stylesheets/common/ai-features.scss @@ -8,7 +8,7 @@ display: block; } - &__row-item-persona { + &__row-item-agent { padding: 0; text-align: left; diff --git a/assets/stylesheets/modules/ai-bot-conversations/common.scss b/assets/stylesheets/modules/ai-bot-conversations/common.scss index c6f0a02d4..c94320a5c 100644 --- a/assets/stylesheets/modules/ai-bot-conversations/common.scss +++ b/assets/stylesheets/modules/ai-bot-conversations/common.scss @@ -157,7 +157,7 @@ body.has-ai-conversations-sidebar { flex-direction: column; height: calc(100dvh - var(--header-offset) - 5em); - .persona-llm-selector { + .agent-llm-selector { display: flex; gap: 0.5em; justify-content: flex-start; diff --git a/assets/stylesheets/modules/ai-bot/common/ai-persona.scss b/assets/stylesheets/modules/ai-bot/common/ai-agent.scss similarity index 93% rename from assets/stylesheets/modules/ai-bot/common/ai-persona.scss rename to assets/stylesheets/modules/ai-bot/common/ai-agent.scss index a5fdfe5bb..ac93b5586 100644 --- a/assets/stylesheets/modules/ai-bot/common/ai-persona.scss +++ b/assets/stylesheets/modules/ai-bot/common/ai-agent.scss @@ -1,8 +1,8 @@ -.admin-contents .ai-persona-list-editor { +.admin-contents .ai-agent-list-editor { margin-top: 0; } -.ai-persona-list-editor { +.ai-agent-list-editor { &__header { display: flex; justify-content: space-between; @@ -23,7 +23,7 @@ } } -.ai-persona-tool-option-editor { +.ai-agent-tool-option-editor { &__instructions { color: var(--primary-medium); font-size: var(--font-down-1); @@ -31,7 +31,7 @@ } } -.ai-personas__container { +.ai-agents__container { display: flex; flex-direction: row; align-items: center; @@ -39,7 +39,7 @@ width: 100%; } -.ai-persona-editor { +.ai-agent-editor { padding-left: 0.5em; &__tool-options { diff --git a/assets/stylesheets/modules/ai-bot/common/bot-replies.scss b/assets/stylesheets/modules/ai-bot/common/bot-replies.scss index 07d0cc2c7..04f03ec31 100644 --- a/assets/stylesheets/modules/ai-bot/common/bot-replies.scss +++ b/assets/stylesheets/modules/ai-bot/common/bot-replies.scss @@ -11,7 +11,7 @@ nav.post-controls .actions button.cancel-streaming { } } - .persona-llm-selector { + .agent-llm-selector { display: flex; justify-content: space-between; align-items: center; @@ -24,7 +24,7 @@ nav.post-controls .actions button.cancel-streaming { } .ai-bot-pm { - .gpt-persona { + .gpt-agent { margin-bottom: 5px; } @@ -75,7 +75,7 @@ article.streaming nav.post-controls .actions button.cancel-streaming { } } -.topic-body .persona-flair { +.topic-body .agent-flair { order: 2; font-size: var(--font-down-1); } diff --git a/assets/stylesheets/modules/ai-bot/mobile/ai-persona.scss b/assets/stylesheets/modules/ai-bot/mobile/ai-agent.scss similarity index 81% rename from assets/stylesheets/modules/ai-bot/mobile/ai-persona.scss rename to assets/stylesheets/modules/ai-bot/mobile/ai-agent.scss index 6353f5267..94e322d12 100644 --- a/assets/stylesheets/modules/ai-bot/mobile/ai-persona.scss +++ b/assets/stylesheets/modules/ai-bot/mobile/ai-agent.scss @@ -1,4 +1,4 @@ -.ai-persona-editor { +.ai-agent-editor { &__system_prompt, &__description, .select-kit.multi-select { diff --git a/assets/stylesheets/modules/llms/common/ai-llms-editor.scss b/assets/stylesheets/modules/llms/common/ai-llms-editor.scss index ac7d86691..b957c5115 100644 --- a/assets/stylesheets/modules/llms/common/ai-llms-editor.scss +++ b/assets/stylesheets/modules/llms/common/ai-llms-editor.scss @@ -45,7 +45,7 @@ } .ai-tool-list-editor__current, -.ai-persona-list-editor__current, +.ai-agent-list-editor__current, .ai-llms-list-editor__configured { .d-admin-table { tr:hover { diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 88214c5f6..24edecde8 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -6,8 +6,8 @@ en: descriptions: discourse_ai: search: "Allows AI search" - stream_completion: "Allows streaming AI persona completions" - update_personas: "Allows updating AI personas" + stream_completion: "Allows streaming AI agent completions" + update_agents: "Allows updating AI agents" site_settings: categories: @@ -100,17 +100,17 @@ en: label: "Tool" description: "Tool to use for triage (tool must have no parameters defined)" - llm_persona_triage: + llm_agent_triage: fields: - persona: - label: "Persona" - description: "AI Persona to use for triage (must have default LLM and User set)" + agent: + label: "Agent" + description: "AI Agent to use for triage (must have default LLM and User set)" whisper: label: "Reply as Whisper" - description: "Whether the persona's response should be a whisper" + description: "Whether the agent's response should be a whisper" silent_mode: label: "Silent Mode" - description: "In silent mode persona will receive the content but will not post anything on the forum - useful when performing triage using tools" + description: "In silent mode agent will receive the content but will not post anything on the forum - useful when performing triage using tools" llm_triage: fields: system_prompt: @@ -146,15 +146,15 @@ en: flag_post: label: "Flag post" description: "Flags post (either as spam or for review)" - include_personal_messages: - label: "Include personal messages" - description: "Also scan and triage personal messages" + include_agentl_messages: + label: "Include agentl messages" + description: "Also scan and triage agentl messages" whisper: label: "Reply as Whisper" description: "Whether the AI's response should be a whisper" - reply_persona: - label: "Reply Persona" - description: "AI Persona to use for replies (must have default LLM), will be prioritized over canned reply" + reply_agent: + label: "Reply Agent" + description: "AI Agent to use for replies (must have default LLM), will be prioritized over canned reply" model: label: "Model" description: "Language model used for triage" @@ -167,12 +167,12 @@ en: features: short_title: "Features" - description: "These are the AI features available to visitors on your site. These can be configured to use specific personas and LLMs, and can be access controlled by groups." + description: "These are the AI features available to visitors on your site. These can be configured to use specific agents and LLMs, and can be access controlled by groups." back: "Back" list: header: name: "Name" - persona: "Persona" + agent: "Agent" groups: "Groups" edit: "Edit" set_up: "Set up" @@ -257,7 +257,7 @@ en: last_month: "Last month" custom: "Custom..." - ai_persona: + ai_agent: ai_tools: "Tools" tool_strategies: all: "Apply to all replies" @@ -269,7 +269,7 @@ en: edit: "Edit" description: "Description" no_llm_selected: "No language model selected" - use_parent_llm: "Use personas language model" + use_parent_llm: "Use agents language model" max_context_posts: "Max context posts" max_context_posts_help: "The maximum number of posts to use as context for the AI when responding to a user. (empty for default)" vision_enabled: Vision enabled @@ -282,47 +282,47 @@ en: tool_details: Show tool details tool_details_help: Will show end users details on which tools the language model has triggered. mentionable: Allow mentions - mentionable_help: If enabled, users in allowed groups can mention this user in posts, the AI will respond as this persona. + mentionable_help: If enabled, users in allowed groups can mention this user in posts, the AI will respond as this agent. user: User create_user: Create user - create_user_help: You can optionally attach a user to this persona. If you do, the AI will use this user to respond to requests. + create_user_help: You can optionally attach a user to this agent. If you do, the AI will use this user to respond to requests. default_llm: Default language model - default_llm_help: The default language model to use for this persona. Required if you wish to mention persona on public posts. + default_llm_help: The default language model to use for this agent. Required if you wish to mention agent on public posts. question_consolidator_llm: Language Model for Question Consolidator question_consolidator_llm_help: The language model to use for the question consolidator, you may choose a less powerful model to save costs. system_prompt: System prompt forced_tool_strategy: Forced tool strategy allow_chat_direct_messages: "Allow chat direct messages" - allow_chat_direct_messages_help: "If enabled, users in allowed groups can send direct messages to this persona." + allow_chat_direct_messages_help: "If enabled, users in allowed groups can send direct messages to this agent." allow_chat_channel_mentions: "Allow chat channel mentions" - allow_chat_channel_mentions_help: "If enabled, users in allowed groups can mention this persona in chat channels." - allow_personal_messages: "Allow personal messages" - allow_personal_messages_help: "If enabled, users in allowed groups can send personal messages to this persona." + allow_chat_channel_mentions_help: "If enabled, users in allowed groups can mention this agent in chat channels." + allow_agentl_messages: "Allow agentl messages" + allow_agentl_messages_help: "If enabled, users in allowed groups can send agentl messages to this agent." allow_topic_mentions: "Allow topic mentions" - allow_topic_mentions_help: "If enabled, users in allowed groups can mention this persona in topics." + allow_topic_mentions_help: "If enabled, users in allowed groups can mention this agent in topics." force_default_llm: "Always use default language model" save: "Save" - saved: "Persona saved" + saved: "Agent saved" enabled: "Enabled?" tools: "Enabled tools" forced_tools: "Forced tools" allowed_groups: "Allowed groups" - confirm_delete: "Are you sure you want to delete this persona?" - new: "New persona" - no_personas: "You have not created any personas yet" - title: "Personas" - short_title: "Personas" + confirm_delete: "Are you sure you want to delete this agent?" + new: "New agent" + no_agents: "You have not created any agents yet" + title: "Agents" + short_title: "Agents" delete: "Delete" temperature: "Temperature" temperature_help: "Temperature to use for the LLM. Increase to increase creativity (leave empty to use model default, generally a value from 0.0 to 2.0)" top_p: "Top P" top_p_help: "Top P to use for the LLM, increase to increase randomness (leave empty to use model default, generally a value from 0.0 to 1.0)" priority: "Priority" - priority_help: "Priority personas are displayed to users at the top of the persona list. If multiple personas have priority, they will be sorted alphabetically." + priority_help: "Priority agents are displayed to users at the top of the agent list. If multiple agents have priority, they will be sorted alphabetically." tool_options: "Tool options" rag_conversation_chunks: "Search conversation chunks" rag_conversation_chunks_help: "The number of chunks to use for the RAG model searches. Increase to increase the amount of context the AI can use." - persona_description: "Personas are a powerful feature that allows you to customize the behavior of the AI engine in your Discourse forum. They act as a 'system message' that guides the AI's responses and interactions, helping to create a more personalized and engaging user experience." + agent_description: "Agents are a powerful feature that allows you to customize the behavior of the AI engine in your Discourse forum. They act as a 'system message' that guides the AI's responses and interactions, helping to create a more agentlized and engaging user experience." response_format: title: "JSON response format" no_format: "No JSON format specified" @@ -344,7 +344,7 @@ en: ai_bot: title: "AI bot options" - save_first: "More AI bot options will become available once you save the persona." + save_first: "More AI bot options will become available once you save the agent." rag: title: "RAG" @@ -377,7 +377,7 @@ en: name_help: "Name will show up in the Discourse UI and is the short identifier you will use to find the tool in various settings, it should be distinct (it is required)" new: "New tool" tool_name: "Tool Name" - tool_name_help: "Tool Name is presented to the large language model. It is not distinct, but it is distinct per persona. (persona validates on save)" + tool_name_help: "Tool Name is presented to the large language model. It is not distinct, but it is distinct per agent. (agent validates on save)" description: "Description" description_help: "A clear description of the tool's purpose for the language model" subheader_description: "Tools extend the capabilities of AI bots with user-defined JavaScript functions." @@ -455,7 +455,7 @@ en: ai_bot: "AI bot" ai_helper: "Helper" ai_helper_image_caption: "Image caption" - ai_persona: "Persona (%{persona})" + ai_agent: "Agent (%{agent})" ai_summarization: "Summarize" ai_embeddings_semantic_search: "AI search" ai_spam: "Spam" @@ -681,7 +681,7 @@ en: click_to_run_label: "Run Artifact" ai_bot: - persona: "Persona" + agent: "Agent" llm: "Model" pm_warning: "AI chatbot messages are monitored regularly by moderators." cancel_streaming: "Stop reply" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 4a71b0dba..edfc1a2a8 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -10,9 +10,9 @@ en: llm_tool_triage: title: Triage posts using AI Tool description: "Triage posts using custom logic in an AI tool" - llm_persona_triage: - title: Triage posts using AI Persona - description: "Respond to posts using a specific AI persona" + llm_agent_triage: + title: Triage posts using AI Agent + description: "Respond to posts using a specific AI agent" llm_triage: title: Triage posts using AI description: "Triage posts using a large language model" @@ -76,7 +76,7 @@ en: ai_auto_image_caption_allowed_groups: "Users on these groups can toggle automatic image captioning." ai_embeddings_selected_model: "Use the selected model for generating embeddings." - ai_embeddings_generate_for_pms: "Generate embeddings for personal messages." + ai_embeddings_generate_for_pms: "Generate embeddings for agentl messages." ai_embeddings_semantic_related_topics_enabled: "Use Semantic Search for related topics." ai_embeddings_semantic_related_topics: "Maximum number of topics to show in related topic section." ai_embeddings_backfill_batch_size: "Number of embeddings to backfill every 15 minutes." @@ -88,7 +88,7 @@ en: ai_summarization_enabled: "Enable the summarize feature" ai_summarization_model: "Model to use for summarization" - ai_summarization_persona: "Persona to use for summarize feature" + ai_summarization_agent: "Agent to use for summarize feature" ai_custom_summarization_allowed_groups: "Groups allowed to use create new summaries." ai_pm_summarization_allowed_groups: "Groups allowed to create and view summaries in PMs." ai_summary_gists_enabled: "Generate brief summaries of latest replies in topics automatically" @@ -99,7 +99,7 @@ en: ai_bot_enable_chat_warning: "Display a warning when PM chat is initiated. Can be overriden by editing the translation string: discourse_ai.ai_bot.pm_warning" ai_bot_allowed_groups: "When the GPT Bot has access to the PM, it will reply to members of these groups." ai_bot_debugging_allowed_groups: "Allow these groups to see a debug button on posts which displays the raw AI request and response" - ai_bot_public_sharing_allowed_groups: "Allow these groups to share AI personal messages with the public via a unique publicly available link. Note: if your site requires login, shares will also require login." + ai_bot_public_sharing_allowed_groups: "Allow these groups to share AI agentl messages with the public via a unique publicly available link. Note: if your site requires login, shares will also require login." ai_bot_add_to_header: "Display a button in the header to start a PM with a AI Bot" ai_bot_github_access_token: "GitHub access token for use with GitHub AI tools (required for search support)" @@ -114,7 +114,7 @@ en: ai_discord_app_id: "The ID of the Discord application you would like to connect Discord search to" ai_discord_app_public_key: "The public key of the Discord application you would like to connect Discord search to" ai_discord_search_mode: "Select the search mode to use for Discord search" - ai_discord_search_persona: "The persona to use for Discord search." + ai_discord_search_agent: "The agent to use for Discord search." ai_discord_allowed_guilds: "Discord guilds (servers) where the bot is allowed to search" ai_bot_enable_dedicated_ux: "Allow for full screen bot interface, instead of a PM" @@ -129,7 +129,7 @@ en: description: "This report provides sentiment analysis for posts, grouped by category, with positive, negative, and neutral scores for each post and category." overall_sentiment: title: "Overall sentiment" - description: 'The chart compares the number of posts classified as either positive or negative. These are calculated when positive or negative scores > the set threshold score. This means neutral posts are not shown. Personal messages (PMs) are also excluded. Classified with "cardiffnlp/twitter-roberta-base-sentiment-latest"' + description: 'The chart compares the number of posts classified as either positive or negative. These are calculated when positive or negative scores > the set threshold score. This means neutral posts are not shown. Agentl messages (PMs) are also excluded. Classified with "cardiffnlp/twitter-roberta-base-sentiment-latest"' xaxis: "Positive(%)" yaxis: "Date" emotion_admiration: @@ -267,8 +267,8 @@ en: title: "%{title} - AI Conversation - %{site_name}" errors: not_allowed: "You are not allowed to share this topic" - other_people_in_pm: "Personal messages with other humans cannot be shared publicly" - other_content_in_pm: "Personal messages containing posts from other people cannot be shared publicly" + other_people_in_pm: "Agentl messages with other humans cannot be shared publicly" + other_content_in_pm: "Agentl messages containing posts from other people cannot be shared publicly" failed_to_share: "Failed to share the conversation" conversation_deleted: "Conversation share deleted successfully" spam_detection: @@ -283,10 +283,10 @@ en: reply_error: "Sorry, it looks like our system encountered an unexpected issue while trying to reply.\n\n[details='Error details']\n%{details}\n[/details]" default_pm_prefix: "[Untitled AI bot PM]" thinking: "Thinking..." - personas: + agents: default_llm_required: "Default LLM model is required prior to enabling Chat" - cannot_delete_system_persona: "System personas cannot be deleted, please disable it instead" - cannot_edit_system_persona: "System personas can only be renamed, you may not edit tools or system prompt, instead disable and make a copy" + cannot_delete_system_agent: "System agents cannot be deleted, please disable it instead" + cannot_edit_system_agent: "System agents can only be renamed, you may not edit tools or system prompt, instead disable and make a copy" cannot_have_duplicate_tools: "Can not have duplicate tools" github_helper: name: "GitHub Helper" @@ -326,10 +326,10 @@ en: description: "AI Bot specialized in creating interactive web artifacts" summarizer: name: "Summarizer" - description: "Default persona used to power AI summaries" + description: "Default agent used to power AI summaries" short_summarizer: name: "Summarizer (short form)" - description: "Default persona used to power AI short summaries for topic lists' items" + description: "Default agent used to power AI short summaries for topic lists' items" topic_not_found: "Summary unavailable, topic not found!" summarizing: "Summarizing topic" searching: "Searching for: '%{query}'" @@ -521,7 +521,7 @@ en: other: "We couldn't delete this model because %{settings} are using it. Update the settings and try again." cannot_edit_builtin: "You can't edit a built-in model." - personas: + agents: malformed_examples: "The given examples have the wrong format." embeddings: @@ -554,12 +554,12 @@ en: quota_exceeded: "You have exceeded the quota for this model. Please try again in %{relative_time}." quota_required: "You must specify maximum tokens or usages for this model" no_query_specified: The query parameter is required, please specify it. - no_user_for_persona: The persona specified does not have a user associated with it. - persona_not_found: The persona specified does not exist. Check the persona_name or persona_id params. + no_user_for_agent: The agent specified does not have a user associated with it. + agent_not_found: The agent specified does not exist. Check the agent_name or agent_id params. no_user_specified: The username or the user_unique_id parameter is required, please specify it. user_not_found: The user specified does not exist. Check the username param. - persona_disabled: The persona specified is disabled. Check the persona_name or persona_id params. - no_default_llm: The persona must have a default_llm defined. + agent_disabled: The agent specified is disabled. Check the agent_name or agent_id params. + no_default_llm: The agent must have a default_llm defined. user_not_allowed: The user is not allowed to participate in the topic. prompt_message_length: The message %{idx} is over the 1000 character limit. dashboard: diff --git a/config/routes.rb b/config/routes.rb index a41c966a3..3a034d6d4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -63,12 +63,12 @@ :constraints => StaffConstraint.new scope "/admin/plugins/discourse-ai", constraints: AdminConstraint.new do - resources :ai_personas, + resources :ai_agents, only: %i[index new create edit update destroy], - path: "ai-personas", - controller: "discourse_ai/admin/ai_personas" + path: "ai-agents", + controller: "discourse_ai/admin/ai_agents" - post "/ai-personas/stream-reply" => "discourse_ai/admin/ai_personas#stream_reply" + post "/ai-agents/stream-reply" => "discourse_ai/admin/ai_agents#stream_reply" resources( :ai_tools, @@ -79,10 +79,10 @@ post "/ai-tools/:id/test", to: "discourse_ai/admin/ai_tools#test" - post "/ai-personas/:id/create-user", to: "discourse_ai/admin/ai_personas#create_user" + post "/ai-agents/:id/create-user", to: "discourse_ai/admin/ai_agents#create_user" - put "/ai-personas/:id/files/remove", to: "discourse_ai/admin/ai_personas#remove_file" - get "/ai-personas/:id/files/status", to: "discourse_ai/admin/ai_personas#indexing_status_check" + put "/ai-agents/:id/files/remove", to: "discourse_ai/admin/ai_agents#remove_file" + get "/ai-agents/:id/files/status", to: "discourse_ai/admin/ai_agents#indexing_status_check" post "/rag-document-fragments/files/upload", to: "discourse_ai/admin/rag_document_fragments#upload_file" diff --git a/db/migrate/20250528154009_copy_persona_tables_to_agent.rb b/db/migrate/20250528154009_copy_persona_tables_to_agent.rb new file mode 100644 index 000000000..215ea6ef1 --- /dev/null +++ b/db/migrate/20250528154009_copy_persona_tables_to_agent.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +class CopyPersonaTablesToAgent < ActiveRecord::Migration[7.0] + def up + # Copy the main table structure and data + if table_exists?(:ai_personas) && !table_exists?(:ai_agents) + execute <<~SQL + CREATE TABLE ai_agents AS + SELECT * FROM ai_personas + SQL + + # Copy indexes from ai_personas to ai_agents + execute <<~SQL + CREATE UNIQUE INDEX index_ai_agents_on_id + ON ai_agents USING btree (id) + SQL + + # Copy any other indexes that exist on ai_personas + indexes = execute(<<~SQL).to_a + SELECT indexname, indexdef + FROM pg_indexes + WHERE tablename = 'ai_personas' + AND indexname != 'ai_personas_pkey' + SQL + + indexes.each do |index| + new_index_def = index['indexdef'].gsub('ai_personas', 'ai_agents') + new_index_name = index['indexname'].gsub('ai_personas', 'ai_agents') + new_index_def = new_index_def.gsub(index['indexname'], new_index_name) + execute(new_index_def) + end + end + + # Update polymorphic associations to point to new table + execute <<~SQL + UPDATE rag_document_fragments + SET target_type = 'AiAgent' + WHERE target_type = 'AiPersona' + SQL + + execute <<~SQL + UPDATE upload_references + SET target_type = 'AiAgent' + WHERE target_type = 'AiPersona' + SQL + + # Migrate persona-related site settings to agent equivalents + migrate_site_setting('ai_summarization_persona', 'ai_summarization_agent') + migrate_site_setting('ai_summary_gists_persona', 'ai_summary_gists_agent') + migrate_site_setting('ai_bot_discover_persona', 'ai_bot_discover_agent') + migrate_site_setting('ai_discord_search_persona', 'ai_discord_search_agent') + end + + def down + drop_table :ai_agents if table_exists?(:ai_agents) + + # Revert polymorphic associations + execute <<~SQL + UPDATE rag_document_fragments + SET target_type = 'AiPersona' + WHERE target_type = 'AiAgent' + SQL + + execute <<~SQL + UPDATE upload_references + SET target_type = 'AiPersona' + WHERE target_type = 'AiAgent' + SQL + + # Remove the new agent settings (keep the old persona ones) + ['ai_summarization_agent', 'ai_summary_gists_agent', 'ai_bot_discover_agent', 'ai_discord_search_agent'].each do |setting| + execute "DELETE FROM site_settings WHERE name = '#{setting}'" + end + end + + private + + def migrate_site_setting(old_name, new_name) + execute <<~SQL + INSERT INTO site_settings (name, value, data_type, created_at, updated_at) + SELECT '#{new_name}', value, data_type, NOW(), NOW() + FROM site_settings + WHERE name = '#{old_name}' + AND NOT EXISTS (SELECT 1 FROM site_settings WHERE name = '#{new_name}') + SQL + end +end diff --git a/db/post_migrate/20250528154010_drop_persona_tables.rb b/db/post_migrate/20250528154010_drop_persona_tables.rb new file mode 100644 index 000000000..aea1de079 --- /dev/null +++ b/db/post_migrate/20250528154010_drop_persona_tables.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class DropPersonaTables < ActiveRecord::Migration[7.0] + def up + # Drop the old table after copying to new one + drop_table :ai_personas if table_exists?(:ai_personas) + + # Remove old persona settings after copying to agent settings + old_persona_settings = [ + 'ai_summarization_persona', + 'ai_summary_gists_persona', + 'ai_bot_discover_persona', + 'ai_discord_search_persona' + ] + + old_persona_settings.each do |setting| + execute "DELETE FROM site_settings WHERE name = '#{setting}'" + end + end + + def down + raise ActiveRecord::IrreversibleMigration, "Cannot recreate dropped persona tables and settings" + end +end diff --git a/discourse_automation/llm_persona_triage.rb b/discourse_automation/llm_agent_triage.rb similarity index 71% rename from discourse_automation/llm_persona_triage.rb rename to discourse_automation/llm_agent_triage.rb index cbe6c1ae8..584c2889a 100644 --- a/discourse_automation/llm_persona_triage.rb +++ b/discourse_automation/llm_agent_triage.rb @@ -1,17 +1,17 @@ # frozen_string_literal: true if defined?(DiscourseAutomation) - DiscourseAutomation::Scriptable.add("llm_persona_triage") do + DiscourseAutomation::Scriptable.add("llm_agent_triage") do version 1 run_in_background triggerables %i[post_created_edited] - field :persona, + field :agent, component: :choices, required: true, extra: { - content: DiscourseAi::Automation.available_persona_choices, + content: DiscourseAi::Automation.available_agent_choices, } field :whisper, component: :boolean field :silent_mode, component: :boolean @@ -20,28 +20,28 @@ post = context["post"] next if post&.user&.bot? - persona_id = fields.dig("persona", "value") + agent_id = fields.dig("agent", "value") whisper = !!fields.dig("whisper", "value") silent_mode = !!fields.dig("silent_mode", "value") begin RateLimiter.new( Discourse.system_user, - "llm_persona_triage_#{post.id}", + "llm_agent_triage_#{post.id}", SiteSetting.ai_automation_max_triage_per_post_per_minute, 1.minute, ).performed! RateLimiter.new( Discourse.system_user, - "llm_persona_triage", + "llm_agent_triage", SiteSetting.ai_automation_max_triage_per_minute, 1.minute, ).performed! - DiscourseAi::Automation::LlmPersonaTriage.handle( + DiscourseAi::Automation::LlmAgentTriage.handle( post: post, - persona_id: persona_id, + agent_id: agent_id, whisper: whisper, automation: self.automation, silent_mode: silent_mode, @@ -49,7 +49,7 @@ rescue => e Discourse.warn_exception( e, - message: "llm_persona_triage: skipped triage on post #{post.id}", + message: "llm_agent_triage: skipped triage on post #{post.id}", ) raise e if Rails.env.tests? end diff --git a/discourse_automation/llm_triage.rb b/discourse_automation/llm_triage.rb index 0bfc46e63..144496e6b 100644 --- a/discourse_automation/llm_triage.rb +++ b/discourse_automation/llm_triage.rb @@ -10,7 +10,7 @@ triggerables %i[post_created_edited] # TODO move to triggerables - field :include_personal_messages, component: :boolean + field :include_agentl_messages, component: :boolean # Inputs field :model, @@ -39,11 +39,11 @@ default: "review" field :canned_reply_user, component: :user field :canned_reply, component: :message - field :reply_persona, + field :reply_agent, component: :choices, extra: { content: - DiscourseAi::Automation.available_persona_choices( + DiscourseAi::Automation.available_agent_choices( require_user: false, require_default_llm: true, ), @@ -55,13 +55,13 @@ next if post&.user&.bot? if post.topic.private_message? - include_personal_messages = fields.dig("include_personal_messages", "value") - next if !include_personal_messages + include_agentl_messages = fields.dig("include_agentl_messages", "value") + next if !include_agentl_messages end canned_reply = fields.dig("canned_reply", "value") canned_reply_user = fields.dig("canned_reply_user", "value") - reply_persona_id = fields.dig("reply_persona", "value") + reply_agent_id = fields.dig("reply_agent", "value") whisper = fields.dig("whisper", "value") # nothing to do if we already replied @@ -113,7 +113,7 @@ tags: tags, canned_reply: canned_reply, canned_reply_user: canned_reply_user, - reply_persona_id: reply_persona_id, + reply_agent_id: reply_agent_id, whisper: whisper, hide_topic: hide_topic, flag_post: flag_post, diff --git a/lib/personas/artifact_update_strategies/base.rb b/lib/agents/artifact_update_strategies/base.rb similarity index 98% rename from lib/personas/artifact_update_strategies/base.rb rename to lib/agents/artifact_update_strategies/base.rb index 624830f16..404d0ec37 100644 --- a/lib/personas/artifact_update_strategies/base.rb +++ b/lib/agents/artifact_update_strategies/base.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true module DiscourseAi - module Personas + module Agents module ArtifactUpdateStrategies class InvalidFormatError < StandardError end diff --git a/lib/personas/artifact_update_strategies/diff.rb b/lib/agents/artifact_update_strategies/diff.rb similarity index 99% rename from lib/personas/artifact_update_strategies/diff.rb rename to lib/agents/artifact_update_strategies/diff.rb index af96c0fb4..d8ba29dfb 100644 --- a/lib/personas/artifact_update_strategies/diff.rb +++ b/lib/agents/artifact_update_strategies/diff.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true module DiscourseAi - module Personas + module Agents module ArtifactUpdateStrategies class Diff < Base attr_reader :failed_searches diff --git a/lib/personas/artifact_update_strategies/full.rb b/lib/agents/artifact_update_strategies/full.rb similarity index 99% rename from lib/personas/artifact_update_strategies/full.rb rename to lib/agents/artifact_update_strategies/full.rb index 3942cdf85..85fc4c865 100644 --- a/lib/personas/artifact_update_strategies/full.rb +++ b/lib/agents/artifact_update_strategies/full.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true module DiscourseAi - module Personas + module Agents module ArtifactUpdateStrategies class Full < Base private diff --git a/lib/personas/artist.rb b/lib/agents/artist.rb similarity index 96% rename from lib/personas/artist.rb rename to lib/agents/artist.rb index 93e2361e2..8741e9fe3 100644 --- a/lib/personas/artist.rb +++ b/lib/agents/artist.rb @@ -1,8 +1,8 @@ #frozen_string_literal: true module DiscourseAi - module Personas - class Artist < Persona + module Agents + class Artist < Agent def tools [Tools::Image] end diff --git a/lib/personas/bot.rb b/lib/agents/bot.rb similarity index 90% rename from lib/personas/bot.rb rename to lib/agents/bot.rb index b6e852c51..91e9231d9 100644 --- a/lib/personas/bot.rb +++ b/lib/agents/bot.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module Personas + module Agents class Bot attr_reader :model @@ -13,19 +13,19 @@ class Bot # limit is arbitrary, but 5 which was used in the past was too low MAX_TOOLS = 20 - def self.as(bot_user, persona: DiscourseAi::Personas::General.new, model: nil) - new(bot_user, persona, model) + def self.as(bot_user, agent: DiscourseAi::Agents::General.new, model: nil) + new(bot_user, agent, model) end - def initialize(bot_user, persona, model = nil) + def initialize(bot_user, agent, model = nil) @bot_user = bot_user - @persona = persona + @agent = agent @model = - model || self.class.guess_model(bot_user) || LlmModel.find(@persona.class.default_llm_id) + model || self.class.guess_model(bot_user) || LlmModel.find(@agent.class.default_llm_id) end attr_reader :bot_user - attr_accessor :persona + attr_accessor :agent def llm DiscourseAi::Completions::Llm.proxy(model) @@ -35,12 +35,12 @@ def force_tool_if_needed(prompt, context) return if prompt.tool_choice == :none context.chosen_tools ||= [] - forced_tools = persona.force_tool_use.map { |tool| tool.name } + forced_tools = agent.force_tool_use.map { |tool| tool.name } force_tool = forced_tools.find { |name| !context.chosen_tools.include?(name) } - if force_tool && persona.forced_tool_count > 0 + if force_tool && agent.forced_tool_count > 0 user_turns = prompt.messages.select { |m| m[:type] == :user }.length - force_tool = false if user_turns > persona.forced_tool_count + force_tool = false if user_turns > agent.forced_tool_count end if force_tool @@ -57,7 +57,7 @@ def reply(context, llm_args: {}, &update_blk) end context.cancel_manager ||= DiscourseAi::Completions::CancelManager.new current_llm = llm - prompt = persona.craft_prompt(context, llm: current_llm) + prompt = agent.craft_prompt(context, llm: current_llm) total_completions = 0 ongoing_chain = true @@ -67,11 +67,11 @@ def reply(context, llm_args: {}, &update_blk) llm_kwargs = llm_args.dup llm_kwargs[:user] = user - llm_kwargs[:temperature] = persona.temperature if persona.temperature - llm_kwargs[:top_p] = persona.top_p if persona.top_p + llm_kwargs[:temperature] = agent.temperature if agent.temperature + llm_kwargs[:top_p] = agent.top_p if agent.top_p llm_kwargs[:response_format] = build_json_schema( - persona.response_format, - ) if persona.response_format.present? + agent.response_format, + ) if agent.response_format.present? needs_newlines = false tools_ran = 0 @@ -82,7 +82,7 @@ def reply(context, llm_args: {}, &update_blk) tool_halted = false - allow_partial_tool_calls = persona.allow_partial_tool_calls? + allow_partial_tool_calls = agent.allow_partial_tool_calls? existing_tools = Set.new current_thinking = [] @@ -96,7 +96,7 @@ def reply(context, llm_args: {}, &update_blk) **llm_kwargs, ) do |partial| tool = - persona.find_tool( + agent.find_tool( partial, bot_user: user, llm: current_llm, @@ -183,7 +183,7 @@ def reply(context, llm_args: {}, &update_blk) end def returns_json? - persona.response_format.present? + agent.response_format.present? end private @@ -285,7 +285,7 @@ def invoke_tool(tool, context, &update_blk) def self.guess_model(bot_user) associated_llm = LlmModel.find_by(user_id: bot_user.id) - return if associated_llm.nil? # Might be a persona user. Handled by constructor. + return if associated_llm.nil? # Might be a agent user. Handled by constructor. associated_llm end diff --git a/lib/personas/bot_context.rb b/lib/agents/bot_context.rb similarity index 99% rename from lib/personas/bot_context.rb rename to lib/agents/bot_context.rb index 69d86669a..18bd4a5fd 100644 --- a/lib/personas/bot_context.rb +++ b/lib/agents/bot_context.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module Personas + module Agents class BotContext attr_accessor :messages, :topic_id, diff --git a/lib/personas/creative.rb b/lib/agents/creative.rb similarity index 81% rename from lib/personas/creative.rb rename to lib/agents/creative.rb index 407c22409..30a540159 100644 --- a/lib/personas/creative.rb +++ b/lib/agents/creative.rb @@ -1,8 +1,8 @@ #frozen_string_literal: true module DiscourseAi - module Personas - class Creative < Persona + module Agents + class Creative < Agent def tools [] end diff --git a/lib/personas/dall_e_3.rb b/lib/agents/dall_e_3.rb similarity index 96% rename from lib/personas/dall_e_3.rb rename to lib/agents/dall_e_3.rb index 851756c82..85a907d0e 100644 --- a/lib/personas/dall_e_3.rb +++ b/lib/agents/dall_e_3.rb @@ -1,8 +1,8 @@ #frozen_string_literal: true module DiscourseAi - module Personas - class DallE3 < Persona + module Agents + class DallE3 < Agent def tools [Tools::DallE] end diff --git a/lib/personas/designer.rb b/lib/agents/designer.rb similarity index 94% rename from lib/personas/designer.rb rename to lib/agents/designer.rb index f2aa8dea4..042de6788 100644 --- a/lib/personas/designer.rb +++ b/lib/agents/designer.rb @@ -1,8 +1,8 @@ #frozen_string_literal: true module DiscourseAi - module Personas - class Designer < Persona + module Agents + class Designer < Agent def tools [Tools::CreateImage, Tools::EditImage] end diff --git a/lib/personas/discourse_helper.rb b/lib/agents/discourse_helper.rb similarity index 97% rename from lib/personas/discourse_helper.rb rename to lib/agents/discourse_helper.rb index 2e9db1422..246b1c3c8 100644 --- a/lib/personas/discourse_helper.rb +++ b/lib/agents/discourse_helper.rb @@ -1,8 +1,8 @@ #frozen_string_literal: true module DiscourseAi - module Personas - class DiscourseHelper < Persona + module Agents + class DiscourseHelper < Agent def tools [Tools::DiscourseMetaSearch] end diff --git a/lib/personas/forum_researcher.rb b/lib/agents/forum_researcher.rb similarity index 97% rename from lib/personas/forum_researcher.rb rename to lib/agents/forum_researcher.rb index a94d9e46e..fde18043f 100644 --- a/lib/personas/forum_researcher.rb +++ b/lib/agents/forum_researcher.rb @@ -1,8 +1,8 @@ #frozen_string_literal: true module DiscourseAi - module Personas - class ForumResearcher < Persona + module Agents + class ForumResearcher < Agent def self.default_enabled false end diff --git a/lib/personas/general.rb b/lib/agents/general.rb similarity index 94% rename from lib/personas/general.rb rename to lib/agents/general.rb index 01c05e08b..6d7840c3d 100644 --- a/lib/personas/general.rb +++ b/lib/agents/general.rb @@ -1,8 +1,8 @@ #frozen_string_literal: true module DiscourseAi - module Personas - class General < Persona + module Agents + class General < Agent def tools [ Tools::Search, diff --git a/lib/personas/github_helper.rb b/lib/agents/github_helper.rb similarity index 94% rename from lib/personas/github_helper.rb rename to lib/agents/github_helper.rb index bd75624d0..e2f53455c 100644 --- a/lib/personas/github_helper.rb +++ b/lib/agents/github_helper.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true module DiscourseAi - module Personas - class GithubHelper < Persona + module Agents + class GithubHelper < Agent def tools [ Tools::GithubFileContent, diff --git a/lib/personas/persona.rb b/lib/agents/persona.rb similarity index 90% rename from lib/personas/persona.rb rename to lib/agents/persona.rb index 62426f77d..8d4433f2d 100644 --- a/lib/personas/persona.rb +++ b/lib/agents/persona.rb @@ -1,8 +1,8 @@ #frozen_string_literal: true module DiscourseAi - module Personas - class Persona + module Agents + class Agent class << self def default_enabled true @@ -36,8 +36,8 @@ def allow_chat_direct_messages false end - def system_personas - @system_personas ||= { + def system_agents + @system_agents ||= { General => -1, SqlHelper => -2, Artist => -3, @@ -55,17 +55,17 @@ def system_personas } end - def system_personas_by_id - @system_personas_by_id ||= system_personas.invert + def system_agents_by_id + @system_agents_by_id ||= system_agents.invert end def all(user:) # listing tools has to be dynamic cause site settings may change - AiPersona.all_personas.filter do |persona| - next false if !user.in_any_groups?(persona.allowed_group_ids) + AiAgent.all_agents.filter do |agent| + next false if !user.in_any_groups?(agent.allowed_group_ids) - if persona.system - instance = persona.new + if agent.system + instance = agent.new ( instance.required_tools == [] || (instance.required_tools - all_available_tools).empty? @@ -77,15 +77,15 @@ def all(user:) end def find_by(id: nil, name: nil, user:) - all(user: user).find { |persona| persona.id == id || persona.name == name } + all(user: user).find { |agent| agent.id == id || agent.name == name } end def name - I18n.t("discourse_ai.ai_bot.personas.#{to_s.demodulize.underscore}.name") + I18n.t("discourse_ai.ai_bot.agents.#{to_s.demodulize.underscore}.name") end def description - I18n.t("discourse_ai.ai_bot.personas.#{to_s.demodulize.underscore}.description") + I18n.t("discourse_ai.ai_bot.agents.#{to_s.demodulize.underscore}.description") end def all_available_tools @@ -134,8 +134,8 @@ def all_available_tools end def id - @ai_persona&.id || self.class.system_personas[self.class.superclass] || - self.class.system_personas[self.class] + @ai_agent&.id || self.class.system_agents[self.class.superclass] || + self.class.system_agents[self.class] end def tools @@ -234,7 +234,7 @@ def craft_prompt(context, llm: nil) prompt.max_pixels = self.class.vision_max_pixels if self.class.vision_enabled prompt.tools = available_tools.map(&:signature) if available_tools available_tools.each do |tool| - tool.inject_prompt(prompt: prompt, context: context, persona: self) + tool.inject_prompt(prompt: prompt, context: context, agent: self) end prompt end @@ -307,7 +307,7 @@ def tool_instance(tool_call, bot_user:, llm:, context:, existing_tools:) tool_klass.new( arguments, tool_call_id: function_id || function_name, - persona_options: options[tool_klass].to_h, + agent_options: options[tool_klass].to_h, bot_user: bot_user, llm: llm, context: context, @@ -331,7 +331,7 @@ def strip_quotes(value) def rag_fragments_prompt(conversation_context, llm:, user:) upload_refs = - UploadReference.where(target_id: id, target_type: "AiPersona").pluck(:upload_id) + UploadReference.where(target_id: id, target_type: "AiAgent").pluck(:upload_id) return nil if !DiscourseAi::Embeddings.enabled? return nil if conversation_context.blank? || upload_refs.blank? @@ -346,7 +346,7 @@ def rag_fragments_prompt(conversation_context, llm:, user:) consolidated_question = latest_interactions[0][:content] else consolidated_question = - DiscourseAi::Personas::QuestionConsolidator.consolidate_question( + DiscourseAi::Agents::QuestionConsolidator.consolidate_question( llm, latest_interactions, user, @@ -376,7 +376,7 @@ def rag_fragments_prompt(conversation_context, llm:, user:) interactions_vector, limit: search_limit, offset: 0, - ) { |builder| builder.join(<<~SQL, target_id: id, target_type: "AiPersona") } + ) { |builder| builder.join(<<~SQL, target_id: id, target_type: "AiAgent") } rag_document_fragments ON rag_document_fragments.id = rag_document_fragment_id AND rag_document_fragments.target_id = :target_id AND diff --git a/lib/personas/question_consolidator.rb b/lib/agents/question_consolidator.rb similarity index 99% rename from lib/personas/question_consolidator.rb rename to lib/agents/question_consolidator.rb index f1e0c476b..a2ee95fdc 100644 --- a/lib/personas/question_consolidator.rb +++ b/lib/agents/question_consolidator.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module Personas + module Agents class QuestionConsolidator attr_reader :llm, :messages, :user, :max_tokens diff --git a/lib/personas/researcher.rb b/lib/agents/researcher.rb similarity index 97% rename from lib/personas/researcher.rb rename to lib/agents/researcher.rb index 6650836bd..115e4ae49 100644 --- a/lib/personas/researcher.rb +++ b/lib/agents/researcher.rb @@ -1,8 +1,8 @@ #frozen_string_literal: true module DiscourseAi - module Personas - class Researcher < Persona + module Agents + class Researcher < Agent def tools [Tools::Google, Tools::WebBrowser] end diff --git a/lib/personas/settings_explorer.rb b/lib/agents/settings_explorer.rb similarity index 92% rename from lib/personas/settings_explorer.rb rename to lib/agents/settings_explorer.rb index 77ae45e34..80d25ac55 100644 --- a/lib/personas/settings_explorer.rb +++ b/lib/agents/settings_explorer.rb @@ -1,8 +1,8 @@ #frozen_string_literal: true module DiscourseAi - module Personas - class SettingsExplorer < Persona + module Agents + class SettingsExplorer < Agent def tools [Tools::SettingContext, Tools::SearchSettings] end diff --git a/lib/personas/short_summarizer.rb b/lib/agents/short_summarizer.rb similarity index 96% rename from lib/personas/short_summarizer.rb rename to lib/agents/short_summarizer.rb index 26af56b93..56a4d4158 100644 --- a/lib/personas/short_summarizer.rb +++ b/lib/agents/short_summarizer.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true module DiscourseAi - module Personas - class ShortSummarizer < Persona + module Agents + class ShortSummarizer < Agent def self.default_enabled false end diff --git a/lib/personas/sql_helper.rb b/lib/agents/sql_helper.rb similarity index 95% rename from lib/personas/sql_helper.rb rename to lib/agents/sql_helper.rb index 720bc145b..bb8eef1f3 100644 --- a/lib/personas/sql_helper.rb +++ b/lib/agents/sql_helper.rb @@ -1,8 +1,8 @@ #frozen_string_literal: true module DiscourseAi - module Personas - class SqlHelper < Persona + module Agents + class SqlHelper < Agent def self.schema return @schema if defined?(@schema) @@ -74,7 +74,7 @@ def system_prompt ``` The user_actions tables stores likes (action_type 1). - The topics table stores private/personal messages it uses archetype private_message for them. + The topics table stores private/agentl messages it uses archetype private_message for them. notification_level can be: {muted: 0, regular: 1, tracking: 2, watching: 3, watching_first_post: 4}. bookmarkable_type can be: Post,Topic,ChatMessage and more diff --git a/lib/personas/summarizer.rb b/lib/agents/summarizer.rb similarity index 97% rename from lib/personas/summarizer.rb rename to lib/agents/summarizer.rb index fe5d496bb..6d42bd5bd 100644 --- a/lib/personas/summarizer.rb +++ b/lib/agents/summarizer.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true module DiscourseAi - module Personas - class Summarizer < Persona + module Agents + class Summarizer < Agent def self.default_enabled false end diff --git a/lib/personas/tool_runner.rb b/lib/agents/tool_runner.rb similarity index 89% rename from lib/personas/tool_runner.rb rename to lib/agents/tool_runner.rb index 6ce684760..2e1cd32e8 100644 --- a/lib/personas/tool_runner.rb +++ b/lib/agents/tool_runner.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module Personas + module Agents class ToolRunner attr_reader :tool, :parameters, :llm attr_accessor :running_attached_function, :timeout, :custom_raw @@ -14,11 +14,11 @@ class ToolRunner MAX_HTTP_REQUESTS = 20 def initialize(parameters:, llm:, bot_user:, context: nil, tool:, timeout: nil) - if context && !context.is_a?(DiscourseAi::Personas::BotContext) + if context && !context.is_a?(DiscourseAi::Agents::BotContext) raise ArgumentError, "context must be a BotContext object" end - context ||= DiscourseAi::Personas::BotContext.new + context ||= DiscourseAi::Agents::BotContext.new @parameters = parameters @llm = llm @@ -82,8 +82,8 @@ def framework_script search: function(params) { return _discourse_search(params); }, - updatePersona: function(persona_id_or_name, updates) { - const result = _discourse_update_persona(persona_id_or_name, updates); + updateAgent: function(agent_id_or_name, updates) { + const result = _discourse_update_agent(agent_id_or_name, updates); if (result.error) { throw new Error(result.error); } @@ -92,29 +92,29 @@ def framework_script getPost: _discourse_get_post, getTopic: _discourse_get_topic, getUser: _discourse_get_user, - getPersona: function(name) { - const personaDetails = _discourse_get_persona(name); - if (personaDetails.error) { - throw new Error(personaDetails.error); + getAgent: function(name) { + const agentDetails = _discourse_get_agent(name); + if (agentDetails.error) { + throw new Error(agentDetails.error); } - // merge result.persona with {}.. + // merge result.agent with {}.. return Object.assign({ update: function(updates) { - const result = _discourse_update_persona(name, updates); + const result = _discourse_update_agent(name, updates); if (result.error) { throw new Error(result.error); } return result; }, respondTo: function(params) { - const result = _discourse_respond_to_persona(name, params); + const result = _discourse_respond_to_agent(name, params); if (result.error) { throw new Error(result.error); } return result; } - }, personaDetails.persona); + }, agentDetails.agent); }, createChatMessage: function(params) { const result = _discourse_create_chat_message(params); @@ -365,15 +365,15 @@ def attach_discourse(mini_racer_context) ) mini_racer_context.attach( - "_discourse_respond_to_persona", - ->(persona_name, params) do + "_discourse_respond_to_agent", + ->(agent_name, params) do in_attached_function do - # if we have 1000s of personas this can be slow ... we may need to optimize - persona_class = AiPersona.all_personas.find { |persona| persona.name == persona_name } - return { error: "Persona not found" } if persona_class.nil? + # if we have 1000s of agents this can be slow ... we may need to optimize + agent_class = AiAgent.all_agents.find { |agent| agent.name == agent_name } + return { error: "Agent not found" } if agent_class.nil? - persona = persona_class.new - bot = DiscourseAi::Personas::Bot.as(@bot_user || persona.user, persona: persona) + agent = agent_class.new + bot = DiscourseAi::Agents::Bot.as(@bot_user || agent.user, agent: agent) playground = DiscourseAi::AiBot::Playground.new(bot) if @context.post_id @@ -479,17 +479,17 @@ def attach_discourse(mini_racer_context) ) mini_racer_context.attach( - "_discourse_get_persona", - ->(persona_name) do + "_discourse_get_agent", + ->(agent_name) do in_attached_function do - persona = AiPersona.find_by(name: persona_name) + agent = AiAgent.find_by(name: agent_name) - return { error: "Persona not found" } if persona.nil? + return { error: "Agent not found" } if agent.nil? - # Return a subset of relevant persona attributes + # Return a subset of relevant agent attributes { - persona: - persona.attributes.slice( + agent: + agent.attributes.slice( "id", "name", "description", @@ -503,7 +503,7 @@ def attach_discourse(mini_racer_context) "allow_chat_channel_mentions", "allow_chat_direct_messages", "allow_topic_mentions", - "allow_personal_messages", + "allow_agentl_messages", ), } end @@ -511,19 +511,19 @@ def attach_discourse(mini_racer_context) ) mini_racer_context.attach( - "_discourse_update_persona", - ->(persona_id_or_name, updates) do + "_discourse_update_agent", + ->(agent_id_or_name, updates) do in_attached_function do - # Find persona by ID or name - persona = nil - if persona_id_or_name.is_a?(Integer) || - persona_id_or_name.to_i.to_s == persona_id_or_name - persona = AiPersona.find_by(id: persona_id_or_name.to_i) + # Find agent by ID or name + agent = nil + if agent_id_or_name.is_a?(Integer) || + agent_id_or_name.to_i.to_s == agent_id_or_name + agent = AiAgent.find_by(id: agent_id_or_name.to_i) else - persona = AiPersona.find_by(name: persona_id_or_name) + agent = AiAgent.find_by(name: agent_id_or_name) end - return { error: "Persona not found" } if persona.nil? + return { error: "Agent not found" } if agent.nil? allowed_updates = {} @@ -545,12 +545,12 @@ def attach_discourse(mini_racer_context) TrueClass, ) || updates["enabled"].is_a?(FalseClass) - if persona.update(allowed_updates) + if agent.update(allowed_updates) return( { success: true, - persona: - persona.attributes.slice( + agent: + agent.attributes.slice( "id", "name", "description", @@ -562,7 +562,7 @@ def attach_discourse(mini_racer_context) } ) else - return { error: persona.errors.full_messages.join(", ") } + return { error: agent.errors.full_messages.join(", ") } end end end, @@ -612,7 +612,7 @@ def attach_http(mini_racer_context) headers = (options && options["headers"]) || {} result = {} - DiscourseAi::Personas::Tools::Tool.send_http_request( + DiscourseAi::Agents::Tools::Tool.send_http_request( url, headers: headers, ) do |response| @@ -641,7 +641,7 @@ def attach_http(mini_racer_context) body = options && options["body"] result = {} - DiscourseAi::Personas::Tools::Tool.send_http_request( + DiscourseAi::Agents::Tools::Tool.send_http_request( url, method: method, headers: headers, diff --git a/lib/personas/tools/create_artifact.rb b/lib/agents/tools/create_artifact.rb similarity index 99% rename from lib/personas/tools/create_artifact.rb rename to lib/agents/tools/create_artifact.rb index 548fb9aab..54d444209 100644 --- a/lib/personas/tools/create_artifact.rb +++ b/lib/agents/tools/create_artifact.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module Personas + module Agents module Tools class CreateArtifact < Tool def self.name diff --git a/lib/personas/tools/create_image.rb b/lib/agents/tools/create_image.rb similarity index 99% rename from lib/personas/tools/create_image.rb rename to lib/agents/tools/create_image.rb index 8e2971fa1..8d76dd37a 100644 --- a/lib/personas/tools/create_image.rb +++ b/lib/agents/tools/create_image.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module Personas + module Agents module Tools class CreateImage < Tool def self.signature diff --git a/lib/personas/tools/custom.rb b/lib/agents/tools/custom.rb similarity index 95% rename from lib/personas/tools/custom.rb rename to lib/agents/tools/custom.rb index 29dbb12d6..b1d322f5e 100644 --- a/lib/personas/tools/custom.rb +++ b/lib/agents/tools/custom.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module Personas + module Agents module Tools class Custom < Tool def self.class_instance(tool_id) @@ -33,7 +33,7 @@ def self.name end def self.has_custom_context? - # note on safety, this can be cached safely, we bump the whole persona cache when an ai tool is saved + # note on safety, this can be cached safely, we bump the whole agent cache when an ai tool is saved # which will expire this class return @has_custom_context if defined?(@has_custom_context) @@ -47,7 +47,7 @@ def self.has_custom_context? @has_custom_context end - def self.inject_prompt(prompt:, context:, persona:) + def self.inject_prompt(prompt:, context:, agent:) if has_custom_context? ai_tool = AiTool.find_by(id: tool_id) if ai_tool diff --git a/lib/personas/tools/dall_e.rb b/lib/agents/tools/dall_e.rb similarity index 99% rename from lib/personas/tools/dall_e.rb rename to lib/agents/tools/dall_e.rb index 1daa7ee17..030ceec94 100644 --- a/lib/personas/tools/dall_e.rb +++ b/lib/agents/tools/dall_e.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module Personas + module Agents module Tools class DallE < Tool def self.signature diff --git a/lib/personas/tools/db_schema.rb b/lib/agents/tools/db_schema.rb similarity index 98% rename from lib/personas/tools/db_schema.rb rename to lib/agents/tools/db_schema.rb index b0a6f296a..30ee49195 100644 --- a/lib/personas/tools/db_schema.rb +++ b/lib/agents/tools/db_schema.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module Personas + module Agents module Tools class DbSchema < Tool def self.signature diff --git a/lib/personas/tools/discourse_meta_search.rb b/lib/agents/tools/discourse_meta_search.rb similarity index 99% rename from lib/personas/tools/discourse_meta_search.rb rename to lib/agents/tools/discourse_meta_search.rb index 5fdfb76ef..80b7c6b8d 100644 --- a/lib/personas/tools/discourse_meta_search.rb +++ b/lib/agents/tools/discourse_meta_search.rb @@ -1,7 +1,7 @@ #frozen_string_literal: true module DiscourseAi - module Personas + module Agents module Tools class DiscourseMetaSearch < Tool class << self diff --git a/lib/personas/tools/edit_image.rb b/lib/agents/tools/edit_image.rb similarity index 99% rename from lib/personas/tools/edit_image.rb rename to lib/agents/tools/edit_image.rb index b9e3249a5..97d7adccb 100644 --- a/lib/personas/tools/edit_image.rb +++ b/lib/agents/tools/edit_image.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module Personas + module Agents module Tools class EditImage < Tool def self.signature diff --git a/lib/personas/tools/github_file_content.rb b/lib/agents/tools/github_file_content.rb similarity index 99% rename from lib/personas/tools/github_file_content.rb rename to lib/agents/tools/github_file_content.rb index aef00c1e6..0be8fd3da 100644 --- a/lib/personas/tools/github_file_content.rb +++ b/lib/agents/tools/github_file_content.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true module DiscourseAi - module Personas + module Agents module Tools class GithubFileContent < Tool def self.signature diff --git a/lib/personas/tools/github_pull_request_diff.rb b/lib/agents/tools/github_pull_request_diff.rb similarity index 99% rename from lib/personas/tools/github_pull_request_diff.rb rename to lib/agents/tools/github_pull_request_diff.rb index afbe51f92..699c1ee7c 100644 --- a/lib/personas/tools/github_pull_request_diff.rb +++ b/lib/agents/tools/github_pull_request_diff.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module Personas + module Agents module Tools class GithubPullRequestDiff < Tool LARGE_OBJECT_THRESHOLD = 30_000 diff --git a/lib/personas/tools/github_search_code.rb b/lib/agents/tools/github_search_code.rb similarity index 99% rename from lib/personas/tools/github_search_code.rb rename to lib/agents/tools/github_search_code.rb index 3bb93d021..be158f4e5 100644 --- a/lib/personas/tools/github_search_code.rb +++ b/lib/agents/tools/github_search_code.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module Personas + module Agents module Tools class GithubSearchCode < Tool def self.signature diff --git a/lib/personas/tools/github_search_files.rb b/lib/agents/tools/github_search_files.rb similarity index 99% rename from lib/personas/tools/github_search_files.rb rename to lib/agents/tools/github_search_files.rb index c97cbd7cf..bcff9cb5d 100644 --- a/lib/personas/tools/github_search_files.rb +++ b/lib/agents/tools/github_search_files.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module Personas + module Agents module Tools class GithubSearchFiles < Tool def self.signature diff --git a/lib/personas/tools/google.rb b/lib/agents/tools/google.rb similarity index 99% rename from lib/personas/tools/google.rb rename to lib/agents/tools/google.rb index bf90fcdbe..642238f40 100644 --- a/lib/personas/tools/google.rb +++ b/lib/agents/tools/google.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module Personas + module Agents module Tools class Google < Tool def self.signature diff --git a/lib/personas/tools/image.rb b/lib/agents/tools/image.rb similarity index 99% rename from lib/personas/tools/image.rb rename to lib/agents/tools/image.rb index 3ab2b7056..8f8883395 100644 --- a/lib/personas/tools/image.rb +++ b/lib/agents/tools/image.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module Personas + module Agents module Tools class Image < Tool def self.signature diff --git a/lib/personas/tools/javascript_evaluator.rb b/lib/agents/tools/javascript_evaluator.rb similarity index 99% rename from lib/personas/tools/javascript_evaluator.rb rename to lib/agents/tools/javascript_evaluator.rb index 23d7c1b1a..33b1e5322 100644 --- a/lib/personas/tools/javascript_evaluator.rb +++ b/lib/agents/tools/javascript_evaluator.rb @@ -4,7 +4,7 @@ require "json" module DiscourseAi - module Personas + module Agents module Tools class JavascriptEvaluator < Tool TIMEOUT = 500 diff --git a/lib/personas/tools/list_categories.rb b/lib/agents/tools/list_categories.rb similarity index 98% rename from lib/personas/tools/list_categories.rb rename to lib/agents/tools/list_categories.rb index 895eaae54..6cd2ab96e 100644 --- a/lib/personas/tools/list_categories.rb +++ b/lib/agents/tools/list_categories.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module Personas + module Agents module Tools class ListCategories < Tool def self.signature diff --git a/lib/personas/tools/list_tags.rb b/lib/agents/tools/list_tags.rb similarity index 97% rename from lib/personas/tools/list_tags.rb rename to lib/agents/tools/list_tags.rb index 852c9abc0..83313ec88 100644 --- a/lib/personas/tools/list_tags.rb +++ b/lib/agents/tools/list_tags.rb @@ -1,7 +1,7 @@ #frozen_string_literal: true module DiscourseAi - module Personas + module Agents module Tools class ListTags < Tool def self.signature diff --git a/lib/personas/tools/option.rb b/lib/agents/tools/option.rb similarity index 97% rename from lib/personas/tools/option.rb rename to lib/agents/tools/option.rb index 777475b84..4c61fe2a1 100644 --- a/lib/personas/tools/option.rb +++ b/lib/agents/tools/option.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module Personas + module Agents module Tools class Option attr_reader :tool, :name, :type, :values, :default diff --git a/lib/personas/tools/random_picker.rb b/lib/agents/tools/random_picker.rb similarity index 99% rename from lib/personas/tools/random_picker.rb rename to lib/agents/tools/random_picker.rb index 1abd537ff..1a9da699f 100644 --- a/lib/personas/tools/random_picker.rb +++ b/lib/agents/tools/random_picker.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module Personas + module Agents module Tools class RandomPicker < Tool def self.signature diff --git a/lib/personas/tools/read.rb b/lib/agents/tools/read.rb similarity index 99% rename from lib/personas/tools/read.rb rename to lib/agents/tools/read.rb index 3077de47f..24bcde52d 100644 --- a/lib/personas/tools/read.rb +++ b/lib/agents/tools/read.rb @@ -1,7 +1,7 @@ #frozen_string_literal: true module DiscourseAi - module Personas + module Agents MAX_POSTS = 100 module Tools diff --git a/lib/personas/tools/read_artifact.rb b/lib/agents/tools/read_artifact.rb similarity index 99% rename from lib/personas/tools/read_artifact.rb rename to lib/agents/tools/read_artifact.rb index e3074d718..fa4dfbb4b 100644 --- a/lib/personas/tools/read_artifact.rb +++ b/lib/agents/tools/read_artifact.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module Personas + module Agents module Tools class ReadArtifact < Tool MAX_HTML_SIZE = 30.kilobytes diff --git a/lib/personas/tools/researcher.rb b/lib/agents/tools/researcher.rb similarity index 99% rename from lib/personas/tools/researcher.rb rename to lib/agents/tools/researcher.rb index d8221a5e2..38a4c5102 100644 --- a/lib/personas/tools/researcher.rb +++ b/lib/agents/tools/researcher.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module Personas + module Agents module Tools class Researcher < Tool attr_reader :filter, :result_count, :goals, :dry_run diff --git a/lib/personas/tools/search.rb b/lib/agents/tools/search.rb similarity index 99% rename from lib/personas/tools/search.rb rename to lib/agents/tools/search.rb index 869cff589..0f4a60dc6 100644 --- a/lib/personas/tools/search.rb +++ b/lib/agents/tools/search.rb @@ -1,7 +1,7 @@ #frozen_string_literal: true module DiscourseAi - module Personas + module Agents module Tools class Search < Tool attr_reader :last_query diff --git a/lib/personas/tools/search_settings.rb b/lib/agents/tools/search_settings.rb similarity index 99% rename from lib/personas/tools/search_settings.rb rename to lib/agents/tools/search_settings.rb index fc66c926c..68e84fbb4 100644 --- a/lib/personas/tools/search_settings.rb +++ b/lib/agents/tools/search_settings.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module Personas + module Agents module Tools class SearchSettings < Tool INCLUDE_DESCRIPTIONS_MAX_LENGTH = 10 diff --git a/lib/personas/tools/setting_context.rb b/lib/agents/tools/setting_context.rb similarity index 99% rename from lib/personas/tools/setting_context.rb rename to lib/agents/tools/setting_context.rb index 7fb7c6b72..2d82850b4 100644 --- a/lib/personas/tools/setting_context.rb +++ b/lib/agents/tools/setting_context.rb @@ -1,7 +1,7 @@ #frozen_string_literal: true module DiscourseAi - module Personas + module Agents module Tools class SettingContext < Tool MAX_CONTEXT_TOKENS = 2000 diff --git a/lib/personas/tools/summarize.rb b/lib/agents/tools/summarize.rb similarity index 99% rename from lib/personas/tools/summarize.rb rename to lib/agents/tools/summarize.rb index 94f6cf493..af237ab10 100644 --- a/lib/personas/tools/summarize.rb +++ b/lib/agents/tools/summarize.rb @@ -1,7 +1,7 @@ #frozen_string_literal: true module DiscourseAi - module Personas + module Agents module Tools class Summarize < Tool def self.signature diff --git a/lib/personas/tools/time.rb b/lib/agents/tools/time.rb similarity index 98% rename from lib/personas/tools/time.rb rename to lib/agents/tools/time.rb index da3d4f43b..50d5dcdd8 100644 --- a/lib/personas/tools/time.rb +++ b/lib/agents/tools/time.rb @@ -1,7 +1,7 @@ #frozen_string_literal: true module DiscourseAi - module Personas + module Agents module Tools class Time < Tool def self.signature diff --git a/lib/personas/tools/tool.rb b/lib/agents/tools/tool.rb similarity index 94% rename from lib/personas/tools/tool.rb rename to lib/agents/tools/tool.rb index 540204c2c..87b71c1e0 100644 --- a/lib/personas/tools/tool.rb +++ b/lib/agents/tools/tool.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module Personas + module Agents module Tools class Tool # Why 30 mega bytes? @@ -43,30 +43,30 @@ def allow_partial_tool_calls? false end - def inject_prompt(prompt:, context:, persona:) + def inject_prompt(prompt:, context:, agent:) end end # llm being public makes it a bit easier to test attr_accessor :custom_raw, :parameters, :llm - attr_reader :tool_call_id, :persona_options, :bot_user, :context + attr_reader :tool_call_id, :agent_options, :bot_user, :context def initialize( parameters, tool_call_id: "", - persona_options: {}, + agent_options: {}, bot_user:, llm:, context: nil ) @parameters = parameters @tool_call_id = tool_call_id - @persona_options = persona_options + @agent_options = agent_options @bot_user = bot_user @llm = llm - @context = context.nil? ? DiscourseAi::Personas::BotContext.new(messages: []) : context - if !@context.is_a?(DiscourseAi::Personas::BotContext) - raise ArgumentError, "context must be a DiscourseAi::Personas::Context" + @context = context.nil? ? DiscourseAi::Agents::BotContext.new(messages: []) : context + if !@context.is_a?(DiscourseAi::Agents::BotContext) + raise ArgumentError, "context must be a DiscourseAi::Agents::Context" end end @@ -89,7 +89,7 @@ def help def options result = HashWithIndifferentAccess.new self.class.accepted_options.each do |option| - val = @persona_options[option.name] + val = @agent_options[option.name] if val case option.type when :boolean diff --git a/lib/personas/tools/update_artifact.rb b/lib/agents/tools/update_artifact.rb similarity index 98% rename from lib/personas/tools/update_artifact.rb rename to lib/agents/tools/update_artifact.rb index 46038d7ab..f054d81cb 100644 --- a/lib/personas/tools/update_artifact.rb +++ b/lib/agents/tools/update_artifact.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module Personas + module Agents module Tools class UpdateArtifact < Tool def self.name @@ -36,8 +36,8 @@ def self.signature } end - def self.inject_prompt(prompt:, context:, persona:) - return if persona.options["do_not_echo_artifact"].to_s == "true" + def self.inject_prompt(prompt:, context:, agent:) + return if agent.options["do_not_echo_artifact"].to_s == "true" # we inject the current artifact content into the last user message if topic_id = context.topic_id posts = Post.where(topic_id: topic_id) diff --git a/lib/personas/tools/web_browser.rb b/lib/agents/tools/web_browser.rb similarity index 99% rename from lib/personas/tools/web_browser.rb rename to lib/agents/tools/web_browser.rb index 15d5ed1eb..7b79a6547 100644 --- a/lib/personas/tools/web_browser.rb +++ b/lib/agents/tools/web_browser.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module Personas + module Agents module Tools class WebBrowser < Tool def self.signature diff --git a/lib/personas/web_artifact_creator.rb b/lib/agents/web_artifact_creator.rb similarity index 97% rename from lib/personas/web_artifact_creator.rb rename to lib/agents/web_artifact_creator.rb index 8fe5ef1ce..8a8708123 100644 --- a/lib/personas/web_artifact_creator.rb +++ b/lib/agents/web_artifact_creator.rb @@ -1,8 +1,8 @@ #frozen_string_literal: true module DiscourseAi - module Personas - class WebArtifactCreator < Persona + module Agents + class WebArtifactCreator < Agent def tools [Tools::CreateArtifact, Tools::UpdateArtifact, Tools::ReadArtifact] end diff --git a/lib/ai_bot/entry_point.rb b/lib/ai_bot/entry_point.rb index 444baaa97..b40208cac 100644 --- a/lib/ai_bot/entry_point.rb +++ b/lib/ai_bot/entry_point.rb @@ -10,9 +10,9 @@ class EntryPoint Bot = Struct.new(:id, :name, :llm) def self.all_bot_ids - AiPersona - .persona_users - .map { |persona| persona[:user_id] } + AiAgent + .agent_users + .map { |agent| agent[:user_id] } .concat(LlmModel.where(enabled_chat_bot: true).pluck(:user_id)) end @@ -101,7 +101,7 @@ def inject_into(plugin) plugin.register_modifier(:chat_allowed_bot_user_ids) do |user_ids, guardian| if guardian.user allowed_chat = - AiPersona.allowed_modalities( + AiAgent.allowed_modalities( user: guardian.user, allow_chat_direct_messages: true, allow_chat_channel_mentions: true, @@ -140,7 +140,7 @@ def inject_into(plugin) :topic_view, :is_bot_pm, include_condition: -> do - object.topic && object.personal_message && + object.topic && object.agentl_message && object.topic.custom_fields[TOPIC_AI_BOT_PM_FIELD] end, ) { true } @@ -155,18 +155,18 @@ def inject_into(plugin) plugin.add_to_serializer( :current_user, - :ai_enabled_personas, + :ai_enabled_agents, include_condition: -> { scope.authenticated? }, ) do - DiscourseAi::Personas::Persona + DiscourseAi::Agents::Agent .all(user: scope.user) - .map do |persona| + .map do |agent| { - id: persona.id, - name: persona.name, - description: persona.description, - force_default_llm: persona.force_default_llm, - username: persona.username, + id: agent.id, + name: agent.name, + description: agent.description, + force_default_llm: agent.force_default_llm, + username: agent.username, } end end @@ -191,17 +191,17 @@ def inject_into(plugin) ) do bots_map = ::DiscourseAi::AiBot::EntryPoint.enabled_user_ids_and_models_map - persona_users = AiPersona.persona_users(user: scope.user) - if persona_users.present? - persona_users.filter! { |persona_user| persona_user[:username].present? } + agent_users = AiAgent.agent_users(user: scope.user) + if agent_users.present? + agent_users.filter! { |agent_user| agent_user[:username].present? } bots_map.concat( - persona_users.map do |persona_user| + agent_users.map do |agent_user| { - "id" => persona_user[:user_id], - "username" => persona_user[:username], - "force_default_llm" => persona_user[:force_default_llm], - "is_persona" => true, + "id" => agent_user[:user_id], + "username" => agent_user[:username], + "force_default_llm" => agent_user[:force_default_llm], + "is_agent" => true, } end, ) @@ -216,16 +216,16 @@ def inject_into(plugin) plugin.add_to_serializer( :current_user, - :can_use_ai_bot_discover_persona, + :can_use_ai_bot_discover_agent, include_condition: -> do SiteSetting.ai_bot_enabled && scope.authenticated? && - SiteSetting.ai_bot_discover_persona.present? + SiteSetting.ai_bot_discover_agent.present? end, ) do - persona_allowed_groups = - AiPersona.find_by(id: SiteSetting.ai_bot_discover_persona)&.allowed_group_ids.to_a + agent_allowed_groups = + AiAgent.find_by(id: SiteSetting.ai_bot_discover_agent)&.allowed_group_ids.to_a - scope.user.in_any_groups?(persona_allowed_groups) + scope.user.in_any_groups?(agent_allowed_groups) end UserUpdater::OPTION_ATTR.push(:ai_search_discoveries) @@ -233,7 +233,7 @@ def inject_into(plugin) :user_option, :ai_search_discoveries, include_condition: -> do - SiteSetting.ai_bot_enabled && SiteSetting.ai_bot_discover_persona.present? && + SiteSetting.ai_bot_enabled && SiteSetting.ai_bot_discover_agent.present? && scope.authenticated? end, ) { object.ai_search_discoveries } @@ -242,19 +242,19 @@ def inject_into(plugin) :current_user_option, :ai_search_discoveries, include_condition: -> do - SiteSetting.ai_bot_enabled && SiteSetting.ai_bot_discover_persona.present? && + SiteSetting.ai_bot_enabled && SiteSetting.ai_bot_discover_agent.present? && scope.authenticated? end, ) { object.ai_search_discoveries } plugin.add_to_serializer( :topic_view, - :ai_persona_name, + :ai_agent_name, include_condition: -> { SiteSetting.ai_bot_enabled && object.topic.private_message? }, ) do - id = topic.custom_fields["ai_persona_id"] - name = DiscourseAi::Personas::Persona.find_by(user: scope.user, id: id.to_i)&.name if id - name || topic.custom_fields["ai_persona"] + id = topic.custom_fields["ai_agent_id"] + name = DiscourseAi::Agents::Agent.find_by(user: scope.user, id: id.to_i)&.name if id + name || topic.custom_fields["ai_agent"] end plugin.on(:post_created) { |post| DiscourseAi::AiBot::Playground.schedule_reply(post) } @@ -264,12 +264,12 @@ def inject_into(plugin) end if plugin.respond_to?(:register_editable_topic_custom_field) - plugin.register_editable_topic_custom_field(:ai_persona_id) + plugin.register_editable_topic_custom_field(:ai_agent_id) end plugin.add_api_key_scope( :discourse_ai, - { stream_completion: { actions: %w[discourse_ai/admin/ai_personas#stream_reply] } }, + { stream_completion: { actions: %w[discourse_ai/admin/ai_agents#stream_reply] } }, ) plugin.on(:site_setting_changed) do |name, old_value, new_value| @@ -277,11 +277,11 @@ def inject_into(plugin) new_value != old_value RagDocumentFragment.delete_all UploadReference - .where(target: AiPersona.all) + .where(target: AiAgent.all) .each do |ref| Jobs.enqueue( :digest_rag_upload, - ai_persona_id: ref.target_id, + ai_agent_id: ref.target_id, upload_id: ref.upload_id, ) end diff --git a/lib/ai_bot/playground.rb b/lib/ai_bot/playground.rb index 07c4984bf..d09fa2e77 100644 --- a/lib/ai_bot/playground.rb +++ b/lib/ai_bot/playground.rb @@ -15,9 +15,9 @@ class Playground # The bot will take care of completions while this class updates the topic title # and stream replies. - def self.find_chat_persona(message, channel, user) + def self.find_chat_agent(message, channel, user) if channel.direct_message_channel? - AiPersona + AiAgent .allowed_modalities(allow_chat_direct_messages: true) .find do |p| p[:user_id].in?(channel.allowed_user_ids) && (user.group_ids & p[:allowed_group_ids]) @@ -27,7 +27,7 @@ def self.find_chat_persona(message, channel, user) if message.message.include?("@") mentions = message.parsed_mentions.parsed_direct_mentions if mentions.present? - AiPersona + AiAgent .allowed_modalities(allow_chat_channel_mentions: true) .find { |p| p[:username].in?(mentions) && (user.group_ids & p[:allowed_group_ids]) } end @@ -39,15 +39,15 @@ def self.schedule_chat_reply(message, channel, user, context) return if !SiteSetting.ai_bot_enabled all_chat = - AiPersona.allowed_modalities( + AiAgent.allowed_modalities( allow_chat_channel_mentions: true, allow_chat_direct_messages: true, ) return if all_chat.blank? return if all_chat.any? { |m| m[:user_id] == user.id } - persona = find_chat_persona(message, channel, user) - return if !persona + agent = find_chat_agent(message, channel, user) + return if !agent post_ids = nil post_ids = context.dig(:context, :post_ids) if context.is_a?(Hash) @@ -56,7 +56,7 @@ def self.schedule_chat_reply(message, channel, user, context) :create_ai_chat_reply, channel_id: channel.id, message_id: message.id, - persona_id: persona[:id], + agent_id: agent[:id], context_post_ids: post_ids, ) end @@ -101,9 +101,9 @@ def self.schedule_reply(post) if post.topic.private_message? mentionables = - AiPersona.allowed_modalities(user: post.user, allow_personal_messages: true) + AiAgent.allowed_modalities(user: post.user, allow_agentl_messages: true) else - mentionables = AiPersona.allowed_modalities(user: post.user, allow_topic_mentions: true) + mentionables = AiAgent.allowed_modalities(user: post.user, allow_topic_mentions: true) end mentioned = nil @@ -135,7 +135,7 @@ def self.schedule_reply(post) mentioned = mentionables.find { |mentionable| bot_user.id == mentionable[:user_id] } end - # public topic so we need to use the persona user + # public topic so we need to use the agent user bot_user ||= User.find_by(id: mentioned[:user_id]) if mentioned end @@ -145,25 +145,25 @@ def self.schedule_reply(post) end if bot_user - topic_persona_id = post.topic.custom_fields["ai_persona_id"] - topic_persona_id = topic_persona_id.to_i if topic_persona_id.present? + topic_agent_id = post.topic.custom_fields["ai_agent_id"] + topic_agent_id = topic_agent_id.to_i if topic_agent_id.present? - persona_id = mentioned&.dig(:id) || topic_persona_id + agent_id = mentioned&.dig(:id) || topic_agent_id - persona = nil + agent = nil - if persona_id - persona = DiscourseAi::Personas::Persona.find_by(user: post.user, id: persona_id.to_i) + if agent_id + agent = DiscourseAi::Agents::Agent.find_by(user: post.user, id: agent_id.to_i) end - if !persona && persona_name = post.topic.custom_fields["ai_persona"] - persona = DiscourseAi::Personas::Persona.find_by(user: post.user, name: persona_name) + if !agent && agent_name = post.topic.custom_fields["ai_agent"] + agent = DiscourseAi::Agents::Agent.find_by(user: post.user, name: agent_name) end - # edge case, llm was mentioned in an ai persona conversation - if persona_id == topic_persona_id && post.topic.private_message? && persona && + # edge case, llm was mentioned in an ai agent conversation + if agent_id == topic_agent_id && post.topic.private_message? && agent && all_llm_users.present? - if !persona.force_default_llm && mentions.present? + if !agent.force_default_llm && mentions.present? mentioned_llm_user_id, _ = all_llm_users.find { |id, username| mentions.include?(username) } @@ -173,11 +173,11 @@ def self.schedule_reply(post) end end - persona ||= DiscourseAi::Personas::General + agent ||= DiscourseAi::Agents::General - bot_user = User.find(persona.user_id) if persona && persona.force_default_llm + bot_user = User.find(agent.user_id) if agent && agent.force_default_llm - bot = DiscourseAi::Personas::Bot.as(bot_user, persona: persona.new) + bot = DiscourseAi::Agents::Bot.as(bot_user, agent: agent.new) new(bot).update_playground_with(post) end end @@ -185,7 +185,7 @@ def self.schedule_reply(post) def self.reply_to_post( post:, user: nil, - persona_id: nil, + agent_id: nil, whisper: nil, add_user_to_pm: false, stream_reply: false, @@ -193,14 +193,14 @@ def self.reply_to_post( silent_mode: false, feature_name: nil ) - ai_persona = AiPersona.find_by(id: persona_id) - raise Discourse::InvalidParameters.new(:persona_id) if !ai_persona - persona_class = ai_persona.class_instance - persona = persona_class.new + ai_agent = AiAgent.find_by(id: agent_id) + raise Discourse::InvalidParameters.new(:agent_id) if !ai_agent + agent_class = ai_agent.class_instance + agent = agent_class.new - bot_user = user || ai_persona.user + bot_user = user || ai_agent.user raise Discourse::InvalidParameters.new(:user) if bot_user.nil? - bot = DiscourseAi::Personas::Bot.as(bot_user, persona: persona) + bot = DiscourseAi::Agents::Bot.as(bot_user, agent: agent) playground = new(bot) playground.reply_to( @@ -236,7 +236,7 @@ def title_playground(post, user) post, max_posts: 5, bot_usernames: available_bot_usernames, - include_uploads: bot.persona.class.vision_enabled, + include_uploads: bot.agent.class.vision_enabled, ) # conversation context may contain tool calls, and confusing user names @@ -297,15 +297,15 @@ def title_playground(post, user) end def reply_to_chat_message(message, channel, context_post_ids) - persona_user = User.find(bot.persona.class.user_id) + agent_user = User.find(bot.agent.class.user_id) participants = channel.user_chat_channel_memberships.map { |m| m.user.username } context_post_ids = nil if !channel.direct_message_channel? max_chat_messages = 40 - if bot.persona.class.respond_to?(:max_context_posts) - max_chat_messages = bot.persona.class.max_context_posts || 40 + if bot.agent.class.respond_to?(:max_context_posts) + max_chat_messages = bot.agent.class.max_context_posts || 40 end if !channel.direct_message_channel? @@ -314,7 +314,7 @@ def reply_to_chat_message(message, channel, context_post_ids) end context = - DiscourseAi::Personas::BotContext.new( + DiscourseAi::Agents::BotContext.new( participants: participants, message_id: message.id, channel_id: channel.id, @@ -324,7 +324,7 @@ def reply_to_chat_message(message, channel, context_post_ids) message, channel: channel, context_post_ids: context_post_ids, - include_uploads: bot.persona.class.vision_enabled, + include_uploads: bot.agent.class.vision_enabled, max_messages: max_chat_messages, bot_user_ids: available_bot_user_ids, instruction_message: instruction_message, @@ -335,7 +335,7 @@ def reply_to_chat_message(message, channel, context_post_ids) ) reply = nil - guardian = Guardian.new(persona_user) + guardian = Guardian.new(agent_user) force_thread = message.thread_id.nil? && channel.direct_message_channel? in_reply_to_id = channel.direct_message_channel? ? message.id : nil @@ -410,12 +410,12 @@ def reply_to( # safeguard max_context_posts = 40 - if bot.persona.class.respond_to?(:max_context_posts) - max_context_posts = bot.persona.class.max_context_posts || 40 + if bot.agent.class.respond_to?(:max_context_posts) + max_context_posts = bot.agent.class.max_context_posts || 40 end context = - DiscourseAi::Personas::BotContext.new( + DiscourseAi::Agents::BotContext.new( post: post, custom_instructions: custom_instructions, feature_name: feature_name, @@ -424,19 +424,19 @@ def reply_to( post, style: context_style, max_posts: max_context_posts, - include_uploads: bot.persona.class.vision_enabled, + include_uploads: bot.agent.class.vision_enabled, bot_usernames: available_bot_usernames, ), ) reply_user = bot.bot_user - if bot.persona.class.respond_to?(:user_id) - reply_user = User.find_by(id: bot.persona.class.user_id) || reply_user + if bot.agent.class.respond_to?(:user_id) + reply_user = User.find_by(id: bot.agent.class.user_id) || reply_user end stream_reply = post.topic.private_message? if stream_reply.nil? - # we need to ensure persona user is allowed to reply to the pm + # we need to ensure agent user is allowed to reply to the pm if post.topic.private_message? && add_user_to_pm if !post.topic.topic_allowed_users.exists?(user_id: reply_user.id) post.topic.topic_allowed_users.create!(user_id: reply_user.id) @@ -486,7 +486,7 @@ def reply_to( ) end - context.skip_tool_details ||= !bot.persona.class.tool_details + context.skip_tool_details ||= !bot.agent.class.tool_details post_streamer = PostStreamer.new(delay: Rails.env.test? ? 0 : 0.5) if stream_reply started_thinking = false @@ -587,11 +587,11 @@ def reply_to( def available_bot_usernames @bot_usernames ||= - AiPersona.joins(:user).pluck(:username).concat(available_bot_users.map(&:username)) + AiAgent.joins(:user).pluck(:username).concat(available_bot_users.map(&:username)) end def available_bot_user_ids - @bot_ids ||= AiPersona.joins(:user).pluck("users.id").concat(available_bot_users.map(&:id)) + @bot_ids ||= AiAgent.joins(:user).pluck("users.id").concat(available_bot_users.map(&:id)) end private @@ -624,13 +624,13 @@ def can_attach?(post) end def schedule_bot_reply(post) - persona_id = - DiscourseAi::Personas::Persona.system_personas[bot.persona.class] || bot.persona.class.id + agent_id = + DiscourseAi::Agents::Agent.system_agents[bot.agent.class] || bot.agent.class.id ::Jobs.enqueue( :create_ai_reply, post_id: post.id, bot_user_id: bot.bot_user.id, - persona_id: persona_id, + agent_id: agent_id, ) end diff --git a/lib/ai_bot/response_http_streamer.rb b/lib/ai_bot/response_http_streamer.rb index cfb2fbebc..9fbd60ef2 100644 --- a/lib/ai_bot/response_http_streamer.rb +++ b/lib/ai_bot/response_http_streamer.rb @@ -31,7 +31,7 @@ def schedule_block(&block) # this allows us to release memory earlier def queue_streamed_reply( io:, - persona:, + agent:, user:, topic:, query:, @@ -53,7 +53,7 @@ def queue_streamed_reply( else post_params[:title] = I18n.t("discourse_ai.ai_bot.default_pm_prefix") post_params[:archetype] = Archetype.private_message - post_params[:target_usernames] = "#{user.username},#{persona.user.username}" + post_params[:target_usernames] = "#{user.username},#{agent.user.username}" end post = PostCreator.create!(user, post_params) @@ -76,15 +76,15 @@ def queue_streamed_reply( io.write CRLF io.flush - persona_class = - DiscourseAi::Personas::Persona.find_by(id: persona.id, user: current_user) - bot = DiscourseAi::Personas::Bot.as(persona.user, persona: persona_class.new) + agent_class = + DiscourseAi::Agents::Agent.find_by(id: agent.id, user: current_user) + bot = DiscourseAi::Agents::Bot.as(agent.user, agent: agent_class.new) data = { topic_id: topic.id, - bot_user_id: persona.user.id, - persona_id: persona.id, + bot_user_id: agent.user.id, + agent_id: agent.id, }.to_json + "\n\n" io.write data.bytesize.to_s(16) diff --git a/lib/automation.rb b/lib/automation.rb index e43bcbc58..0752590d1 100644 --- a/lib/automation.rb +++ b/lib/automation.rb @@ -42,15 +42,15 @@ def self.available_models values end - def self.available_persona_choices(require_user: true, require_default_llm: true) - relation = AiPersona.joins(:user) + def self.available_agent_choices(require_user: true, require_default_llm: true) + relation = AiAgent.joins(:user) relation = relation.where.not(user_id: nil) if require_user relation = relation.where.not(default_llm: nil) if require_default_llm - relation.map do |persona| + relation.map do |agent| { - id: persona.id, - translated_name: persona.name, - description: "#{persona.name} (#{persona.user.username})", + id: agent.id, + translated_name: agent.name, + description: "#{agent.name} (#{agent.user.username})", } end end diff --git a/lib/automation/llm_persona_triage.rb b/lib/automation/llm_agent_triage.rb similarity index 64% rename from lib/automation/llm_persona_triage.rb rename to lib/automation/llm_agent_triage.rb index dbbdbc6ae..decb24eb6 100644 --- a/lib/automation/llm_persona_triage.rb +++ b/lib/automation/llm_agent_triage.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true module DiscourseAi module Automation - module LlmPersonaTriage - def self.handle(post:, persona_id:, whisper: false, silent_mode: false, automation: nil) + module LlmAgentTriage + def self.handle(post:, agent_id:, whisper: false, silent_mode: false, automation: nil) DiscourseAi::AiBot::Playground.reply_to_post( post: post, - persona_id: persona_id, + agent_id: agent_id, whisper: whisper, silent_mode: silent_mode, feature_name: "automation - #{automation&.name}", @@ -13,7 +13,7 @@ def self.handle(post:, persona_id:, whisper: false, silent_mode: false, automati rescue => e Discourse.warn_exception( e, - message: "Error responding to: #{post&.url} in LlmPersonaTriage.handle", + message: "Error responding to: #{post&.url} in LlmAgentTriage.handle", ) raise e if Rails.env.test? nil diff --git a/lib/automation/llm_tool_triage.rb b/lib/automation/llm_tool_triage.rb index 58b9210d5..a8eb4eee1 100644 --- a/lib/automation/llm_tool_triage.rb +++ b/lib/automation/llm_tool_triage.rb @@ -7,7 +7,7 @@ def self.handle(post:, tool_id:, automation: nil) return if !tool return if !tool.parameters.blank? - context = DiscourseAi::Personas::BotContext.new(post: post) + context = DiscourseAi::Agents::BotContext.new(post: post) runner = tool.runner({}, llm: nil, bot_user: Discourse.system_user, context: context) runner.invoke diff --git a/lib/automation/llm_triage.rb b/lib/automation/llm_triage.rb index 20ab9ae49..05cd87de2 100644 --- a/lib/automation/llm_triage.rb +++ b/lib/automation/llm_triage.rb @@ -20,11 +20,11 @@ def self.handle( stop_sequences: nil, temperature: nil, whisper: nil, - reply_persona_id: nil, + reply_agent_id: nil, action: nil ) if category_id.blank? && tags.blank? && canned_reply.blank? && hide_topic.blank? && - flag_post.blank? && reply_persona_id.blank? + flag_post.blank? && reply_agent_id.blank? raise ArgumentError, "llm_triage: no action specified!" end @@ -69,11 +69,11 @@ def self.handle( user = User.find_by_username(canned_reply_user) if canned_reply_user.present? original_user = user user = user || Discourse.system_user - if reply_persona_id.present? && action != :edit + if reply_agent_id.present? && action != :edit begin DiscourseAi::AiBot::Playground.reply_to_post( post: post, - persona_id: reply_persona_id, + agent_id: reply_agent_id, whisper: whisper, user: original_user, ) diff --git a/lib/configuration/persona_enumerator.rb b/lib/configuration/agent_enumerator.rb similarity index 53% rename from lib/configuration/persona_enumerator.rb rename to lib/configuration/agent_enumerator.rb index c115bc50a..b25bdf4de 100644 --- a/lib/configuration/persona_enumerator.rb +++ b/lib/configuration/agent_enumerator.rb @@ -4,15 +4,15 @@ module DiscourseAi module Configuration - class PersonaEnumerator < ::EnumSiteSetting + class AgentEnumerator < ::EnumSiteSetting def self.valid_value?(val) true end def self.values - AiPersona - .all_personas(enabled_only: false) - .map { |persona| { name: persona.name, value: persona.id } } + AiAgent + .all_agents(enabled_only: false) + .map { |agent| { name: agent.name, value: agent.id } } end end end diff --git a/lib/configuration/llm_enumerator.rb b/lib/configuration/llm_enumerator.rb index 200fc0a27..6fa13c32a 100644 --- a/lib/configuration/llm_enumerator.rb +++ b/lib/configuration/llm_enumerator.rb @@ -16,10 +16,10 @@ def self.global_usage end # this is unconditional, so it is clear that we always signal configuration - AiPersona + AiAgent .where("default_llm_id IS NOT NULL") .pluck(:default_llm_id, :name, :id) - .each { |llm_id, name, id| rval[llm_id] << { type: :ai_persona, name: name, id: id } } + .each { |llm_id, name, id| rval[llm_id] << { type: :ai_agent, name: name, id: id } } if SiteSetting.ai_helper_enabled model_id = SiteSetting.ai_helper_model.split(":").last.to_i @@ -32,8 +32,8 @@ def self.global_usage end if SiteSetting.ai_summarization_enabled - summarization_persona = AiPersona.find_by(id: SiteSetting.ai_summarization_persona) - model_id = summarization_persona.default_llm_id || LlmModel.last&.id + summarization_agent = AiAgent.find_by(id: SiteSetting.ai_summarization_agent) + model_id = summarization_agent.default_llm_id || LlmModel.last&.id rval[model_id] << { type: :ai_summarization } end diff --git a/lib/discord/bot/persona_replier.rb b/lib/discord/bot/agent_replier.rb similarity index 78% rename from lib/discord/bot/persona_replier.rb rename to lib/discord/bot/agent_replier.rb index b64af15c1..5ebd1b2fe 100644 --- a/lib/discord/bot/persona_replier.rb +++ b/lib/discord/bot/agent_replier.rb @@ -2,18 +2,18 @@ module DiscourseAi module Discord::Bot - class PersonaReplier < Base + class AgentReplier < Base def initialize(body) - @persona = - AiPersona - .all_personas(enabled_only: false) - .find { |persona| persona.id == SiteSetting.ai_discord_search_persona.to_i } + @agent = + AiAgent + .all_agents(enabled_only: false) + .find { |agent| agent.id == SiteSetting.ai_discord_search_agent.to_i } .new @bot = - DiscourseAi::Personas::Bot.as( + DiscourseAi::Agents::Bot.as( Discourse.system_user, - persona: @persona, - model: LlmModel.find(@persona.class.default_llm_id), + agent: @agent, + model: LlmModel.find(@agent.class.default_llm_id), ) super(body) end diff --git a/lib/discord/bot/search.rb b/lib/discord/bot/search.rb index e33e35e73..e214f0026 100644 --- a/lib/discord/bot/search.rb +++ b/lib/discord/bot/search.rb @@ -4,7 +4,7 @@ module DiscourseAi module Discord::Bot class Search < Base def initialize(body) - @search = DiscourseAi::Personas::Tools::Search + @search = DiscourseAi::Agents::Tools::Search super(body) end @@ -12,7 +12,7 @@ def handle_interaction! results = @search.new( { search_query: @query }, - persona_options: { + agent_options: { "max_results" => 10, }, bot_user: nil, diff --git a/lib/features.rb b/lib/features.rb index d3b999c25..2095d9e4b 100644 --- a/lib/features.rb +++ b/lib/features.rb @@ -9,7 +9,7 @@ def self.feature_config name_ref: "summarization", name_key: "discourse_ai.features.summarization.name", description_key: "discourse_ai.features.summarization.description", - persona_setting_name: "ai_summarization_persona", + agent_setting_name: "ai_summarization_agent", enable_setting_name: "ai_summarization_enabled", }, { @@ -17,7 +17,7 @@ def self.feature_config name_ref: "gists", name_key: "discourse_ai.features.gists.name", description_key: "discourse_ai.features.gists.description", - persona_setting_name: "ai_summary_gists_persona", + agent_setting_name: "ai_summary_gists_agent", enable_setting_name: "ai_summary_gists_enabled", }, { @@ -25,7 +25,7 @@ def self.feature_config name_ref: "discoveries", name_key: "discourse_ai.features.discoveries.name", description_key: "discourse_ai.features.discoveries.description", - persona_setting_name: "ai_bot_discover_persona", + agent_setting_name: "ai_bot_discover_agent", enable_setting_name: "ai_bot_enabled", }, { @@ -33,7 +33,7 @@ def self.feature_config name_ref: "discord_search", name_key: "discourse_ai.features.discord_search.name", description_key: "discourse_ai.features.discord_search.description", - persona_setting_name: "ai_discord_search_persona", + agent_setting_name: "ai_discord_search_agent", enable_setting_name: "ai_discord_search_enabled", }, ] @@ -46,11 +46,11 @@ def self.features ref: feature[:name_ref], name: I18n.t(feature[:name_key]), description: I18n.t(feature[:description_key]), - persona: AiPersona.find_by(id: SiteSetting.get(feature[:persona_setting_name])), - persona_setting: { - name: feature[:persona_setting_name], - value: SiteSetting.get(feature[:persona_setting_name]), - type: SiteSetting.type_supervisor.get_type(feature[:persona_setting_name]), + agent: AiAgent.find_by(id: SiteSetting.get(feature[:agent_setting_name])), + agent_setting: { + name: feature[:agent_setting_name], + value: SiteSetting.get(feature[:agent_setting_name]), + type: SiteSetting.type_supervisor.get_type(feature[:agent_setting_name]), }, enable_setting: { name: feature[:enable_setting_name], diff --git a/lib/guardian_extensions.rb b/lib/guardian_extensions.rb index 45cb1b55b..7956d3ccb 100644 --- a/lib/guardian_extensions.rb +++ b/lib/guardian_extensions.rb @@ -25,25 +25,25 @@ def can_see_gists? return false if !SiteSetting.ai_summarization_enabled return false if !SiteSetting.ai_summary_gists_enabled - if (ai_persona = AiPersona.find_by(id: SiteSetting.ai_summary_gists_persona)).blank? + if (ai_agent = AiAgent.find_by(id: SiteSetting.ai_summary_gists_agent)).blank? return false end - persona_groups = ai_persona.allowed_group_ids.to_a - return true if persona_groups.include?(Group::AUTO_GROUPS[:everyone]) + agent_groups = ai_agent.allowed_group_ids.to_a + return true if agent_groups.include?(Group::AUTO_GROUPS[:everyone]) return false if anonymous? - persona_groups.any? { |group_id| user.group_ids.include?(group_id) } + agent_groups.any? { |group_id| user.group_ids.include?(group_id) } end def can_request_summary? return false if anonymous? user_group_ids = user.group_ids - if (ai_persona = AiPersona.find_by(id: SiteSetting.ai_summarization_persona)).blank? + if (ai_agent = AiAgent.find_by(id: SiteSetting.ai_summarization_agent)).blank? return false end - ai_persona.allowed_group_ids.to_a.any? { |group_id| user.group_ids.include?(group_id) } + ai_agent.allowed_group_ids.to_a.any? { |group_id| user.group_ids.include?(group_id) } end def can_debug_ai_bot_conversation?(target) diff --git a/lib/summarization.rb b/lib/summarization.rb index a7b697638..0b2ceea62 100644 --- a/lib/summarization.rb +++ b/lib/summarization.rb @@ -5,60 +5,60 @@ module Summarization class << self def topic_summary(topic) return nil if !SiteSetting.ai_summarization_enabled - if (ai_persona = AiPersona.find_by(id: SiteSetting.ai_summarization_persona)).blank? + if (ai_agent = AiAgent.find_by(id: SiteSetting.ai_summarization_agent)).blank? return nil end - persona_klass = ai_persona.class_instance - llm_model = find_summarization_model(persona_klass) + agent_klass = ai_agent.class_instance + llm_model = find_summarization_model(agent_klass) return nil if llm_model.blank? DiscourseAi::Summarization::FoldContent.new( - build_bot(persona_klass, llm_model), + build_bot(agent_klass, llm_model), DiscourseAi::Summarization::Strategies::TopicSummary.new(topic), ) end def topic_gist(topic) return nil if !SiteSetting.ai_summarization_enabled - if (ai_persona = AiPersona.find_by(id: SiteSetting.ai_summary_gists_persona)).blank? + if (ai_agent = AiAgent.find_by(id: SiteSetting.ai_summary_gists_agent)).blank? return nil end - persona_klass = ai_persona.class_instance - llm_model = find_summarization_model(persona_klass) + agent_klass = ai_agent.class_instance + llm_model = find_summarization_model(agent_klass) return nil if llm_model.blank? DiscourseAi::Summarization::FoldContent.new( - build_bot(persona_klass, llm_model), + build_bot(agent_klass, llm_model), DiscourseAi::Summarization::Strategies::HotTopicGists.new(topic), ) end def chat_channel_summary(channel, time_window_in_hours) return nil if !SiteSetting.ai_summarization_enabled - if (ai_persona = AiPersona.find_by(id: SiteSetting.ai_summarization_persona)).blank? + if (ai_agent = AiAgent.find_by(id: SiteSetting.ai_summarization_agent)).blank? return nil end - persona_klass = ai_persona.class_instance - llm_model = find_summarization_model(persona_klass) + agent_klass = ai_agent.class_instance + llm_model = find_summarization_model(agent_klass) return nil if llm_model.blank? DiscourseAi::Summarization::FoldContent.new( - build_bot(persona_klass, llm_model), + build_bot(agent_klass, llm_model), DiscourseAi::Summarization::Strategies::ChatMessages.new(channel, time_window_in_hours), persist_summaries: false, ) end # Priorities are: - # 1. Persona's default LLM + # 1. Agent's default LLM # 2. Hidden `ai_summarization_model` setting # 3. Newest LLM config - def find_summarization_model(persona_klass) + def find_summarization_model(agent_klass) model_id = - persona_klass.default_llm_id || SiteSetting.ai_summarization_model&.split(":")&.last # Remove legacy custom provider. + agent_klass.default_llm_id || SiteSetting.ai_summarization_model&.split(":")&.last # Remove legacy custom provider. if model_id.present? LlmModel.find_by(id: model_id) @@ -69,11 +69,11 @@ def find_summarization_model(persona_klass) ### Private - def build_bot(persona_klass, llm_model) - persona = persona_klass.new - user = User.find_by(id: persona_klass.user_id) || Discourse.system_user + def build_bot(agent_klass, llm_model) + agent = agent_klass.new + user = User.find_by(id: agent_klass.user_id) || Discourse.system_user - bot = DiscourseAi::Personas::Bot.as(user, persona: persona, model: llm_model) + bot = DiscourseAi::Agents::Bot.as(user, agent: agent, model: llm_model) end end end diff --git a/lib/summarization/entry_point.rb b/lib/summarization/entry_point.rb index ca9cd6d50..e83830034 100644 --- a/lib/summarization/entry_point.rb +++ b/lib/summarization/entry_point.rb @@ -7,10 +7,10 @@ def inject_into(plugin) plugin.add_to_serializer(:current_user, :can_summarize) do return false if !SiteSetting.ai_summarization_enabled - if (ai_persona = AiPersona.find_by(id: SiteSetting.ai_summarization_persona)).blank? + if (ai_agent = AiAgent.find_by(id: SiteSetting.ai_summarization_agent)).blank? return false end - scope.user.in_any_groups?(ai_persona.allowed_group_ids.to_a) + scope.user.in_any_groups?(ai_agent.allowed_group_ids.to_a) end plugin.add_to_serializer(:topic_view, :summarizable) do diff --git a/lib/summarization/fold_content.rb b/lib/summarization/fold_content.rb index 17df679bb..460b29048 100644 --- a/lib/summarization/fold_content.rb +++ b/lib/summarization/fold_content.rb @@ -101,7 +101,7 @@ def fold(items, user, &on_partial_blk) end context = - DiscourseAi::Personas::BotContext.new( + DiscourseAi::Agents::BotContext.new( user: user, skip_tool_details: true, feature_name: strategy.feature, @@ -114,7 +114,7 @@ def fold(items, user, &on_partial_blk) buffer_blk = Proc.new do |partial, _, type| if type == :structured_output - json_summary_schema_key = bot.persona.response_format&.first.to_h + json_summary_schema_key = bot.agent.response_format&.first.to_h partial_summary = partial.read_buffered_property(json_summary_schema_key["key"]&.to_sym) diff --git a/plugin.rb b/plugin.rb index 8510dbf90..0bd79f718 100644 --- a/plugin.rb +++ b/plugin.rb @@ -39,9 +39,9 @@ register_asset "stylesheets/modules/summarization/common/ai-gists.scss" register_asset "stylesheets/modules/ai-bot/common/bot-replies.scss" -register_asset "stylesheets/modules/ai-bot/common/ai-persona.scss" +register_asset "stylesheets/modules/ai-bot/common/ai-agent.scss" register_asset "stylesheets/modules/ai-bot/common/ai-discobot-discoveries.scss" -register_asset "stylesheets/modules/ai-bot/mobile/ai-persona.scss", :mobile +register_asset "stylesheets/modules/ai-bot/mobile/ai-agent.scss", :mobile register_asset "stylesheets/modules/ai-bot-conversations/common.scss" @@ -87,11 +87,11 @@ def self.public_asset_path(name) require_relative "discourse_automation/llm_triage" require_relative "discourse_automation/llm_report" require_relative "discourse_automation/llm_tool_triage" - require_relative "discourse_automation/llm_persona_triage" + require_relative "discourse_automation/llm_agent_triage" add_admin_route("discourse_ai.title", "discourse-ai", { use_new_show_route: true }) - register_seedfu_fixtures(Rails.root.join("plugins", "discourse-ai", "db", "fixtures", "personas")) + register_seedfu_fixtures(Rails.root.join("plugins", "discourse-ai", "db", "fixtures", "agents")) [ DiscourseAi::Embeddings::EntryPoint.new, @@ -137,7 +137,7 @@ def self.public_asset_path(name) add_api_key_scope( :discourse_ai, - { update_personas: { actions: %w[discourse_ai/admin/ai_personas#update] } }, + { update_agents: { actions: %w[discourse_ai/admin/ai_agents#update] } }, ) plugin_icons = %w[ diff --git a/rename_persona_to_agent.rb b/rename_persona_to_agent.rb new file mode 100755 index 000000000..6d10b6d49 --- /dev/null +++ b/rename_persona_to_agent.rb @@ -0,0 +1,388 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "fileutils" +require "date" + +class PersonaToAgentRenamer + def initialize + @plugin_root = Dir.pwd + @manifest = [] + end + + def run + case ARGV[0] + when "--content-only" + validate_directory + replace_content_in_files + print_content_summary + when "--migrations-only" + validate_directory + create_migration + create_post_migration + print_migration_summary + else + puts colorize("=" * 55, :blue) + puts colorize("Renaming 'persona' to 'agent' throughout the codebase", :blue) + puts colorize("=" * 55, :blue) + puts colorize("Working in: #{@plugin_root}", :blue) + + validate_directory + replace_content_in_files + rename_files_and_directories + create_migration + create_post_migration + print_summary + end + end + + private + + def colorize(text, color) + colors = { + red: "\033[0;31m", + green: "\033[0;32m", + yellow: "\033[0;33m", + blue: "\033[0;34m", + nc: "\033[0m", + } + "#{colors[color]}#{text}#{colors[:nc]}" + end + + def validate_directory + unless File.exist?(File.join(@plugin_root, "plugin.rb")) + puts colorize("Error: Script must be run from the discourse-ai plugin root directory", :red) + exit 1 + end + end + + def git_tracked_files + `git ls-files`.split("\n").map { |f| File.join(@plugin_root, f) } + end + + def all_directories_with_persona + # Find all directories that contain 'persona' in their name + output = `find . -type d -name "*persona*" 2>/dev/null | grep -v "\\.git"`.split("\n") + output.map { |dir| File.join(@plugin_root, dir.sub("./", "")) } + end + + def replace_content_in_files + puts colorize("Replacing content in files...", :blue) + + file_extensions = %w[rb js gjs yml erb scss hbs json] + + git_tracked_files.each do |file| + # Skip files in db/ directory and tokenizers/ + next if file.include?("/db/") + next if file.include?("/tokenizers/") + next if !file_extensions.include?(File.extname(file)[1..-1]) + next if File.basename(file) == "rename_persona_to_agent.rb" + + # For YAML files, only process client.en.yml and server.en.yml + if File.extname(file) == ".yml" + basename = File.basename(file) + next unless basename == "client.en.yml" || basename == "server.en.yml" + end + + next unless File.exist?(file) + + content = File.read(file) + next unless content.match?(/persona/i) + + original_content = content.dup + + # Replace different case variations + content.gsub!(/persona/, "agent") + content.gsub!(/Persona/, "Agent") + content.gsub!(/PERSONA/, "AGENT") + + # Handle special cases + content.gsub!(/aiPersona/, "aiAgent") + content.gsub!(/AIPersona/, "AIAgent") + content.gsub!(/ai_persona/, "ai_agent") + content.gsub!(/Ai_persona/, "Ai_agent") + content.gsub!(/ai-persona/, "ai-agent") + + if content != original_content + File.write(file, content) + relative_path = file.sub("#{@plugin_root}/", "") + puts colorize("Content updated: #{relative_path}", :green) + @manifest << "Content updated: #{relative_path}" + end + end + end + + def rename_files_and_directories + puts colorize("Renaming files and directories using git mv...", :blue) + + # Get all directories with 'persona' in their path (excluding db/ and tokenizers/) + # Sort by depth (deepest first) to avoid conflicts when renaming parent directories + dirs_to_rename = + all_directories_with_persona + .select { |path| !path.include?("/db/") && !path.include?("/tokenizers/") } + .sort_by { |path| -path.count("/") } + + # Get all files with 'persona' in their names (excluding db/ and tokenizers/) + files_to_rename = + git_tracked_files.select do |path| + !path.include?("/db/") && !path.include?("/tokenizers/") && + File.basename(path).match?(/persona/i) + end + + # First, rename individual files that have 'persona' in their filename + puts colorize(" Renaming individual files with 'persona' in filename...", :blue) + files_to_rename.each do |old_path| + next unless File.exist?(old_path) + next if File.basename(old_path) == "rename_persona_to_agent.rb" + + # Skip files that are inside directories we're going to rename + # (they'll be handled when we rename the directory) + next if dirs_to_rename.any? { |dir| old_path.start_with?(dir + "/") } + + new_path = old_path.gsub(/persona/, "agent").gsub(/Persona/, "Agent") + + if old_path != new_path + # Ensure parent directory exists + FileUtils.mkdir_p(File.dirname(new_path)) + + # Use git mv to preserve history + if system("git", "mv", old_path, new_path) + old_relative = old_path.sub("#{@plugin_root}/", "") + new_relative = new_path.sub("#{@plugin_root}/", "") + puts colorize(" File renamed: #{old_relative} -> #{new_relative}", :green) + @manifest << "File renamed: #{old_relative} -> #{new_relative}" + else + puts colorize(" Failed to rename: #{old_path}", :red) + end + end + end + + # Then rename directories (deepest first to avoid path conflicts) + puts colorize(" Renaming directories with 'persona' in path...", :blue) + dirs_to_rename.each do |old_dir_path| + next unless File.exist?(old_dir_path) && File.directory?(old_dir_path) + + new_dir_path = old_dir_path.gsub(/persona/, "agent").gsub(/Persona/, "Agent") + + if old_dir_path != new_dir_path && !File.exist?(new_dir_path) + # Create parent directory if needed + FileUtils.mkdir_p(File.dirname(new_dir_path)) + + # Use git mv to preserve history for the entire directory tree + if system("git", "mv", old_dir_path, new_dir_path) + old_relative = old_dir_path.sub("#{@plugin_root}/", "") + new_relative = new_dir_path.sub("#{@plugin_root}/", "") + puts colorize(" Directory renamed: #{old_relative} -> #{new_relative}", :green) + @manifest << "Directory renamed: #{old_relative} -> #{new_relative}" + + # Log all files that were moved as part of this directory rename + if File.directory?(new_dir_path) + Dir + .glob("#{new_dir_path}/**/*", File::FNM_DOTMATCH) + .each do |moved_file| + next if File.directory?(moved_file) + next if File.basename(moved_file).start_with?(".") + + # Calculate what the old path would have been + relative_to_new_dir = moved_file.sub(new_dir_path + "/", "") + old_file_path = File.join(old_dir_path, relative_to_new_dir) + + old_file_relative = old_file_path.sub("#{@plugin_root}/", "") + new_file_relative = moved_file.sub("#{@plugin_root}/", "") + puts colorize( + " File moved: #{old_file_relative} -> #{new_file_relative}", + :green, + ) + @manifest << "File moved: #{old_file_relative} -> #{new_file_relative}" + end + end + else + puts colorize(" Failed to rename directory: #{old_dir_path}", :red) + end + end + end + end + + def create_migration + puts colorize("Creating database migration to copy persona tables and settings...", :blue) + + timestamp = (Time.now - 86_400).strftime("%Y%m%d%H%M%S") # Yesterday to avoid UTC issues + migration_file = + File.join(@plugin_root, "db", "migrate", "#{timestamp}_copy_persona_tables_to_agent.rb") + + migration_content = generate_migration_content + + FileUtils.mkdir_p(File.dirname(migration_file)) + File.write(migration_file, migration_content) + + relative_migration = migration_file.sub("#{@plugin_root}/", "") + puts colorize("Created migration file: #{relative_migration}", :green) + @manifest << "Created migration file: #{relative_migration}" + end + + def generate_migration_content + <<~RUBY + # frozen_string_literal: true + + class CopyPersonaTablesToAgent < ActiveRecord::Migration[7.0] + def up + # Copy the main table structure and data + if table_exists?(:ai_personas) && !table_exists?(:ai_agents) + execute <<~SQL + CREATE TABLE ai_agents AS + SELECT * FROM ai_personas + SQL + + # Copy indexes from ai_personas to ai_agents + execute <<~SQL + CREATE UNIQUE INDEX index_ai_agents_on_id + ON ai_agents USING btree (id) + SQL + + # Copy any other indexes that exist on ai_personas + indexes = execute(<<~SQL).to_a + SELECT indexname, indexdef + FROM pg_indexes + WHERE tablename = 'ai_personas' + AND indexname != 'ai_personas_pkey' + SQL + + indexes.each do |index| + new_index_def = index['indexdef'].gsub('ai_personas', 'ai_agents') + new_index_name = index['indexname'].gsub('ai_personas', 'ai_agents') + new_index_def = new_index_def.gsub(index['indexname'], new_index_name) + execute(new_index_def) + end + end + + # Update polymorphic associations to point to new table + execute <<~SQL + UPDATE rag_document_fragments + SET target_type = 'AiAgent' + WHERE target_type = 'AiPersona' + SQL + + execute <<~SQL + UPDATE upload_references + SET target_type = 'AiAgent' + WHERE target_type = 'AiPersona' + SQL + + # Migrate persona-related site settings to agent equivalents + migrate_site_setting('ai_summarization_persona', 'ai_summarization_agent') + migrate_site_setting('ai_summary_gists_persona', 'ai_summary_gists_agent') + migrate_site_setting('ai_bot_discover_persona', 'ai_bot_discover_agent') + migrate_site_setting('ai_discord_search_persona', 'ai_discord_search_agent') + end + + def down + drop_table :ai_agents if table_exists?(:ai_agents) + + # Revert polymorphic associations + execute <<~SQL + UPDATE rag_document_fragments + SET target_type = 'AiPersona' + WHERE target_type = 'AiAgent' + SQL + + execute <<~SQL + UPDATE upload_references + SET target_type = 'AiPersona' + WHERE target_type = 'AiAgent' + SQL + + # Remove the new agent settings (keep the old persona ones) + ['ai_summarization_agent', 'ai_summary_gists_agent', 'ai_bot_discover_agent', 'ai_discord_search_agent'].each do |setting| + execute "DELETE FROM site_settings WHERE name = '\#{setting}'" + end + end + + private + + def migrate_site_setting(old_name, new_name) + execute <<~SQL + INSERT INTO site_settings (name, value, data_type, created_at, updated_at) + SELECT '\#{new_name}', value, data_type, NOW(), NOW() + FROM site_settings + WHERE name = '\#{old_name}' + AND NOT EXISTS (SELECT 1 FROM site_settings WHERE name = '\#{new_name}') + SQL + end + end + RUBY + end + + def create_post_migration + puts colorize("Creating post-migration to drop old persona tables and settings...", :blue) + + timestamp = (Time.now - 86_399).strftime("%Y%m%d%H%M%S") # Yesterday + 1 second to ensure this runs after the main migration + post_migrate_dir = File.join(@plugin_root, "db", "post_migrate") + migration_file = File.join(post_migrate_dir, "#{timestamp}_drop_persona_tables.rb") + + migration_content = generate_post_migration_content + + FileUtils.mkdir_p(post_migrate_dir) + File.write(migration_file, migration_content) + + relative_migration = migration_file.sub("#{@plugin_root}/", "") + puts colorize("Created post-migration file: #{relative_migration}", :green) + @manifest << "Created post-migration file: #{relative_migration}" + end + + def generate_post_migration_content + <<~RUBY + # frozen_string_literal: true + + class DropPersonaTables < ActiveRecord::Migration[7.0] + def up + # Drop the old table after copying to new one + drop_table :ai_personas if table_exists?(:ai_personas) + + # Remove old persona settings after copying to agent settings + old_persona_settings = [ + 'ai_summarization_persona', + 'ai_summary_gists_persona', + 'ai_bot_discover_persona', + 'ai_discord_search_persona' + ] + + old_persona_settings.each do |setting| + execute "DELETE FROM site_settings WHERE name = '\#{setting}'" + end + end + + def down + raise ActiveRecord::IrreversibleMigration, "Cannot recreate dropped persona tables and settings" + end + end + RUBY + end + + def print_content_summary + puts colorize("Content replacement completed!", :green) + puts colorize("Files updated: #{@manifest.count}", :yellow) + @manifest.each { |change| puts " #{change}" } + end + + def print_migration_summary + puts colorize("Database migrations created!", :green) + puts colorize("Files created: #{@manifest.count}", :yellow) + @manifest.each { |change| puts " #{change}" } + end + + def print_summary + puts colorize("=" * 55, :blue) + puts colorize("Completed renaming 'persona' to 'agent' in the codebase", :green) + puts colorize("=" * 55, :blue) + puts colorize("Changes made:", :yellow) + @manifest.each { |change| puts " #{change}" } + puts colorize("Next steps:", :yellow) + puts "1. Review changes with 'git diff'" + puts "2. Run tests and fix any remaining issues" + puts "3. Run the database migrations (migrate, then post_migrate)" + puts colorize("=" * 55, :blue) + end +end + +# Run the renamer if this file is executed directly +PersonaToAgentRenamer.new.run if __FILE__ == $0 diff --git a/spec/fabricators/ai_persona_fabricator.rb b/spec/fabricators/ai_agent_fabricator.rb similarity index 57% rename from spec/fabricators/ai_persona_fabricator.rb rename to spec/fabricators/ai_agent_fabricator.rb index 4b3c3f02d..622ba68f7 100644 --- a/spec/fabricators/ai_persona_fabricator.rb +++ b/spec/fabricators/ai_agent_fabricator.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -Fabricator(:ai_persona) do - name { sequence(:name) { |i| "persona_#{i}" } } +Fabricator(:ai_agent) do + name { sequence(:name) { |i| "agent_#{i}" } } description "I am a test bot" system_prompt "You are a test bot" end diff --git a/spec/fixtures/search_meta/search.json b/spec/fixtures/search_meta/search.json index b02212a10..2cb5cf17c 100644 --- a/spec/fixtures/search_meta/search.json +++ b/spec/fixtures/search_meta/search.json @@ -1 +1 @@ -{"posts":[{"id":218354,"name":"Robin Ward","username":"eviltrout","avatar_template":"/user_avatar/meta.discourse.org/eviltrout/{size}/5275_2.png","created_at":"2016-08-24T20:48:02.587Z","like_count":15,"blurb":"Automated tests are a great way to protect your code against future regressions. Many people are familiar with how to do this in our Rails codebase with http://rspec.info/ rspec , but the Javascript s...","post_number":1,"topic_title_headline":"Write acceptance tests and component tests for Ember code in Discourse","topic_id":49167},{"id":138484,"name":"Robin Ward","username":"eviltrout","avatar_template":"/user_avatar/meta.discourse.org/eviltrout/{size}/5275_2.png","created_at":"2015-08-27T21:32:26.407Z","like_count":29,"blurb":"Previous tutorial: https://meta.discourse.org/t/developing-discourse-plugins-part-5-add-an-admin-interface/31761 Developing Discourse Plugins - Part 5 - Add an admin interface Did you know that Discou...","post_number":1,"topic_title_headline":"Developing Discourse Plugins - Part 6 - Add acceptance tests","topic_id":32619},{"id":1381521,"name":"Alan Tan","username":"tgxworld","avatar_template":"/user_avatar/meta.discourse.org/tgxworld/{size}/106117_2.png","created_at":"2023-10-24T23:13:37.118Z","like_count":16,"blurb":"Writing automated tests for themes is an important part of the theme development process which can help ensure that the features being introduced by a theme continues to work well overtime with core D...","post_number":1,"topic_title_headline":"End-to-end system testing for themes and theme components","topic_id":281579},{"id":311252,"name":"David Taylor","username":"david","avatar_template":"/user_avatar/meta.discourse.org/david/{size}/157490_2.png","created_at":"2017-07-26T14:09:58.126Z","like_count":17,"blurb":"Discourse has extensive frontend tests for core, plugins and themes. Once you have a functioning local development environment, those tests can be run locally in a number of different ways. Running te...","post_number":1,"topic_title_headline":"How to run Discourse core, plugin and theme QUnit test suites","topic_id":66857},{"id":59431,"name":"Erlend Sogge Heggen","username":"erlend_sh","avatar_template":"/user_avatar/meta.discourse.org/erlend_sh/{size}/119475_2.png","created_at":"2014-07-03T11:45:09.463Z","like_count":5,"blurb":"...view=1 to an URL, but the emulator has the added benefit of letting you select the screen profile of a specific device. I also tested all of the most popular online screen emulators, but unfortunately...","post_number":1,"topic_title_headline":"Test Discourse in mobile screen emulator","topic_id":17155},{"id":1419473,"name":"","username":"ToddZ","avatar_template":"/user_avatar/meta.discourse.org/toddz/{size}/328350_2.png","created_at":"2023-12-12T10:51:08.401Z","like_count":2,"blurb":"...amount of time troubleshooting inbound email because Discourse was rejecting every reply-by-email from my fake users. It had worked fine when I first tested several weeks ago… I finally realized that ...","post_number":1,"topic_title_headline":"Tip: when testing inbound email with fake user accounts…","topic_id":288363},{"id":722424,"name":"Falco","username":"Falco","avatar_template":"/user_avatar/meta.discourse.org/falco/{size}/179432_2.png","created_at":"2020-03-26T21:31:38.463Z","like_count":17,"blurb":"Continuing the discussion from https://meta.discourse.org/t/user-api-keys-specification/48536 User API keys specification : I created a small utility script in order to test User API keys locally. Fir...","post_number":1,"topic_title_headline":"Generate User API Keys for testing","topic_id":145744},{"id":266441,"name":"Andrew Waugh","username":"JagWaugh","avatar_template":"/user_avatar/meta.discourse.org/jagwaugh/{size}/69335_2.png","created_at":"2017-03-03T13:34:08.009Z","like_count":19,"blurb":"Regardless of if you're a moderator or an admin, you will no doubt at some time think about making some change to your live site and wonder if this will bring shame on you, and/or cause yourself an en...","post_number":1,"topic_title_headline":"Build a sandbox to test changes before making them live","topic_id":58298},{"id":582008,"name":"","username":"Wurzelseppi","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/w/eada6e/{size}.png","created_at":"2019-05-21T10:42:51.099Z","like_count":0,"blurb":"Hi guys, just wanted to migrate from 2.3.0beta9 to stable release and got this error: What can I do here ? Caused by: PG::UndefinedColumn: ERROR: column \"email_private_messages\" of relation \"user_opti...","post_number":1,"topic_title_headline":"Migrate from tests-passed to stable","topic_id":118296},{"id":1421414,"name":"Robert","username":"merefield","avatar_template":"/user_avatar/meta.discourse.org/merefield/{size}/176214_2.png","created_at":"2023-12-14T17:11:26.575Z","like_count":1,"blurb":"Restarting the server, restarting the container, console rebuilding doesn't help. Container is up as I can ./launcher enter app Got a bunch of these in logs, ideas on how to investigate redis failure?...","post_number":1,"topic_title_headline":"502 Bad Gateway after online rebuild of tests-passed Production just now","topic_id":288705},{"id":1204506,"name":"Coin-coin le Canapin","username":"Canapin","avatar_template":"/user_avatar/meta.discourse.org/canapin/{size}/119591_2.png","created_at":"2022-12-02T20:49:55.867Z","like_count":1,"blurb":"Hi! I want to add support of /shorts/ Youtube link. My modification of the YoutubeOnebox class works, but it is required that I add a test in https://github.com/discourse/discourse/blob/493d437e79f88a...","post_number":1,"topic_title_headline":"Trouble on adding a simple unit test for Youtube oneboxing","topic_id":247546},{"id":1339808,"name":"Robert","username":"merefield","avatar_template":"/user_avatar/meta.discourse.org/merefield/{size}/176214_2.png","created_at":"2023-08-07T10:05:22.444Z","like_count":2,"blurb":"I have a strange issue with QUnit. This test is extremely simple and should be straightforward … but … A plugin setting is changing from those I have set up. https://github.com/paviliondev/discourse-l...","post_number":1,"topic_title_headline":"Strange QUnit behaviour?: test failing because setting value doesn’t survive","topic_id":274165},{"id":1211823,"name":"Robert","username":"merefield","avatar_template":"/user_avatar/meta.discourse.org/merefield/{size}/176214_2.png","created_at":"2022-12-16T18:32:29.231Z","like_count":1,"blurb":"...presenting formatted location on User Card by merefield · Pull Request #73 · paviliondev/discourse-locations · GitHub I'm attempting to cover the change with a new Front End test. But the test fails t...","post_number":1,"topic_title_headline":"Is it possible to override the Site object with own fixture during Front End tests of a Plugin?","topic_id":249167},{"id":1408389,"name":"Pierre Romera","username":"pirhoo","avatar_template":"/user_avatar/meta.discourse.org/pirhoo/{size}/120058_2.png","created_at":"2023-11-22T18:46:46.469Z","like_count":5,"blurb":"...m setting up a new plugin based on the https://github.com/discourse/discourse-plugin-skeleton/tree/main/assets skeleton you provided which already helped me a lot. I am now writing tests, both for the...","post_number":1,"topic_title_headline":"Acceptance tests failing on Github Actions","topic_id":286355},{"id":443129,"name":"JK Baseer","username":"JKBaseer","avatar_template":"/user_avatar/meta.discourse.org/jkbaseer/{size}/80471_2.png","created_at":"2018-07-01T17:12:48.446Z","like_count":0,"blurb":"...but still could myself. Background: I installed discourse using digitalocean oneclick installer. The website is running under http://forum.example.org forum.example.org without any problem except the ...","post_number":1,"topic_title_headline":"There was a problem sending the test email","topic_id":91312},{"id":1412219,"name":"Robert","username":"merefield","avatar_template":"/user_avatar/meta.discourse.org/merefield/{size}/176214_2.png","created_at":"2023-11-29T23:18:11.815Z","like_count":1,"blurb":"I'm trying to create a foreign key relationship with the Topics table. The problem is it is failing in github workflow test environment during tests for the strangest reason, it is trying to access a ...","post_number":1,"topic_title_headline":"Strange migration error in tests during GH workflow","topic_id":287022},{"id":1160803,"name":"Bryan Joseph","username":"Bryan_Joseph","avatar_template":"/user_avatar/meta.discourse.org/bryan_joseph/{size}/273248_2.png","created_at":"2022-09-07T20:22:11.191Z","like_count":1,"blurb":"...SETTINGS ==================== DISCOURSE_HOSTNAME=url SMTP_ADDRESS=smtp.mailgun.org DEVELOPER_EMAILS=REDACTED SMTP_PASSWORD=REDACTED SMTP_PORT=2525 SMTP_USER_NAME=url LETSENCRYPT_ACCOUNT_EMAIL=REDACTED...","post_number":1,"topic_title_headline":"Smtp doctor test using port 465 even though its configured to use 2525","topic_id":238372},{"id":725228,"name":"james.network","username":"sunjam","avatar_template":"/user_avatar/meta.discourse.org/sunjam/{size}/175682_2.png","created_at":"2020-03-31T18:51:44.216Z","like_count":0,"blurb":"...Redis or updating it; it hasn't really been touched in the last 8+ months. I have not personally dealt with Redis before, but our Tests-Pass Discourse instance was setup using https://hub.docker.com/r...","post_number":1,"topic_title_headline":"Sidekiq not running. Sidekiq heartbeat test failed, restarting","topic_id":146326},{"id":1240758,"name":"Jay Pfaffman","username":"pfaffman","avatar_template":"/user_avatar/meta.discourse.org/pfaffman/{size}/120154_2.png","created_at":"2023-02-16T22:12:10.456Z","like_count":4,"blurb":"I see that the discourse-plugin-skeleton now has this: uses: discourse/.github/.github/workflows/discourse-plugin.yml@v1 so we don't have to keep updating stuff. But I have a plugin that requires the ...","post_number":1,"topic_title_headline":"Tests for plugin that requires a plugin","topic_id":255406},{"id":1197679,"name":"","username":"SilK","avatar_template":"/user_avatar/meta.discourse.org/silk/{size}/268124_2.png","created_at":"2022-11-18T17:03:46.056Z","like_count":0,"blurb":"...a new dev environment for working on plugins. Discourse is up to date with the main branch. I need to restart Ember in order to test changes made to the front end. This includes changes to Handlebars,...","post_number":1,"topic_title_headline":"Need to restart Ember in order to test front-end changes","topic_id":246069},{"id":1103913,"name":"Banibrata Dutta","username":"bdutta","avatar_template":"/user_avatar/meta.discourse.org/bdutta/{size}/259973_2.png","created_at":"2022-05-15T18:02:33.290Z","like_count":0,"blurb":"...only in a captive host-only testbed, so wondering if there is any local network SMTP daemon / service that I could start to complete the testing ? I'm happy with 100% command line mail client and serv...","post_number":1,"topic_title_headline":"Bitnami Discourse VM on Virtualbox + SMTP mail-server for testing","topic_id":227090},{"id":722608,"name":"Lona Lee","username":"Lona_Lee","avatar_template":"/user_avatar/meta.discourse.org/lona_lee/{size}/169072_2.png","created_at":"2020-03-27T07:14:10.239Z","like_count":1,"blurb":"Hello. I'm trying to get email setup working on my discourse instance. Done set-up properly and looks fine(no errors), so sent test emails. (logs confirmed : \"Admin\" - \"Emails\" - \"Sent\") However, I ha...","post_number":1,"topic_title_headline":"Test emails sent but","topic_id":145781},{"id":679950,"name":"Oleg Bovykin","username":"arrowcircle","avatar_template":"/user_avatar/meta.discourse.org/arrowcircle/{size}/100035_2.png","created_at":"2020-01-01T11:20:33.618Z","like_count":1,"blurb":"Hi! I found strange error in my admin page, that sidekiq is not running. I opened logs and found hundreds errors like: /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/logster-2.5.1/lib/logster/logger...","post_number":1,"topic_title_headline":"Sidekiq heartbeat test failed, restarting","topic_id":137496},{"id":1033333,"name":"М. М.","username":"М_М","avatar_template":"/user_avatar/meta.discourse.org/м_м/{size}/243710_2.png","created_at":"2021-12-20T13:52:10.488Z","like_count":0,"blurb":"...user, the logs say like this Job exception: could not get 3xx (421: 421 Domain sandbox410fe5c7bb85483c941c05b4ec5f3495.mailgun.org is not allowed to send: Sandbox subdomains are for test purposes only...","post_number":1,"topic_title_headline":"MailGun & Discourse: Sandbox subdomains are for test purposes only. Please add your own domain","topic_id":212684},{"id":498559,"name":"","username":"desrocchi","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/d/eb9ed0/{size}.png","created_at":"2018-11-14T15:20:59.607Z","like_count":0,"blurb":"Is there a way for me to see or test the admin options in the demo area? I am just a moderator on the platform we use but I would like to see which options could be of use without having to install th...","post_number":1,"topic_title_headline":"Test admin features without having to install Discourse","topic_id":102035},{"id":596557,"name":"Flaviu","username":"UnivacTwo","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/u/df705f/{size}.png","created_at":"2019-06-25T17:12:34.710Z","like_count":0,"blurb":"Let's encrypt has a limit of how many certificates can be generated in a week for the same domain. Unfortunately we reach this limit and we cannot generate a new certificate this week. We did a backup...","post_number":1,"topic_title_headline":"Install discourse with a staging (test) ssl certificate","topic_id":121299},{"id":583541,"name":"mark78","username":"Mark_Schmucker","avatar_template":"/user_avatar/meta.discourse.org/mark_schmucker/{size}/124810_2.png","created_at":"2019-05-24T00:14:35.895Z","like_count":0,"blurb":"Should I be able to run any Badge Query in https://meta.discourse.org/t/32566 Data Explorer ? I want to create a custom Badge Query using \"Appreciated\" as a starting point. I type the Appreciated quer...","post_number":1,"topic_title_headline":"Problem testing Badge Query from Data Explorer","topic_id":118568},{"id":569209,"name":"Penar Musaraj","username":"pmusaraj","avatar_template":"/user_avatar/meta.discourse.org/pmusaraj/{size}/119489_2.png","created_at":"2019-04-25T01:24:56.741Z","like_count":26,"blurb":"...device and installing the app via TestFlight: https://testflight.apple.com/join/NkdBQgmg testflight.apple.com https://testflight.apple.com/join/NkdBQgmg TestFlight - Apple Using TestFlight is a great ...","post_number":1,"topic_title_headline":"New iOS mobile app beta available for testing","topic_id":115912},{"id":1090520,"name":"Mac玩儿法","username":"waerfa","avatar_template":"/user_avatar/meta.discourse.org/waerfa/{size}/216044_2.png","created_at":"2022-04-17T21:46:04.755Z","like_count":0,"blurb":"...rebuild the container: git pull ./launcher rebuild app I got the fatal error which shows: FAILED -------------------- Pups::ExecError: cd /var/www/discourse & & git fetch --depth 1 origin tests-passed...","post_number":1,"topic_title_headline":"502 Bad Gateway after trying to rebuild test-passed branch","topic_id":224560},{"id":860593,"name":"james.network","username":"sunjam","avatar_template":"/user_avatar/meta.discourse.org/sunjam/{size}/175682_2.png","created_at":"2020-12-11T18:44:18.021Z","like_count":1,"blurb":"Continuing the discussion from https://meta.discourse.org/t/postgresql-13-update/172563/27 PostgreSQL 13 update : Run into trouble while updating 2.7.0beta1 Tests-Pass in order to remove some troubles...","post_number":1,"topic_title_headline":"Forum offline due to failed rebuilds on Tests-Pass","topic_id":173019},{"id":960683,"name":"","username":"daniyal","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/d/58f4c7/{size}.png","created_at":"2021-07-08T19:58:55.634Z","like_count":3,"blurb":"...which we want to experiment. An example would be to experiment different styles of topic list view. For this we are using Google Optimize A/B testing. Currently we plan to show theme without changes t...","post_number":1,"topic_title_headline":"[A/B Testing] Changing parent CSS class based on experiment variable","topic_id":196501},{"id":1389188,"name":"Angus McLeod","username":"angus","avatar_template":"/user_avatar/meta.discourse.org/angus/{size}/341715_2.png","created_at":"2023-10-20T03:44:49.737Z","like_count":7,"blurb":"I've been looking at the performance of the https://meta.discourse.org/t/activitypub-plugin/266794 ActivityPub plugin recently and considering the best ways to reliably test, and prove, performance fo...","post_number":1,"topic_title_headline":"Code-level performance testing","topic_id":282856},{"id":1329017,"name":"","username":"dodibi","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/d/9fc348/{size}.png","created_at":"2023-07-19T12:45:57.446Z","like_count":0,"blurb":"Hello everyone! I'm currently facing some challenges while configuring my local environment to run discourse tests in a docker container. My main objective is to run the core tests with plugins attach...","post_number":1,"topic_title_headline":"Running core tests in docker environment","topic_id":272112},{"id":421687,"name":"Jay Pfaffman","username":"pfaffman","avatar_template":"/user_avatar/meta.discourse.org/pfaffman/{size}/120154_2.png","created_at":"2018-05-11T22:03:23.232Z","like_count":0,"blurb":"Is there a way to initiate an email test from the Rails console? For a zillion reasons I would love to be able to send a test email without having to create an account. I've looked in config/routes.rb...","post_number":1,"topic_title_headline":"Email test from the console?","topic_id":87295},{"id":272436,"name":"David Taylor","username":"david","avatar_template":"/user_avatar/meta.discourse.org/david/{size}/157490_2.png","created_at":"2017-03-20T20:29:06.608Z","like_count":1,"blurb":"It is my understanding that running rake qunit:test should run all of the qunit tests in Discourse, including those for any installed plugins. However, when I run the task in the docker development en...","post_number":1,"topic_title_headline":"Plugin QUnit tests are not running as part of rake qunit:test","topic_id":59577},{"id":899205,"name":"","username":"JQ331","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/j/41988e/{size}.png","created_at":"2021-03-03T18:09:29.452Z","like_count":6,"blurb":"I came across this https://blog.codinghorror.com/low-fi-usability-testing/ excellent article on how to do low-fi usability testing by @codinghorror . Usability testing (and user testing in general) is...","post_number":1,"topic_title_headline":"How does the Discourse team do usability testing?","topic_id":181856},{"id":530438,"name":"","username":"kleinfreund","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/k/a6a055/{size}.png","created_at":"2019-02-01T09:52:27.150Z","like_count":1,"blurb":"One can write tests for the backend of a plugin. For example, I created the following file in my plugin directory: spec/lib/route_store_spec.rb : require 'rails_helper' describe MyPlugin::RouteStore d...","post_number":1,"topic_title_headline":"Advice on writing Ruby tests for plugins","topic_id":108110},{"id":260691,"name":"Rimian Perkins","username":"rimian","avatar_template":"/user_avatar/meta.discourse.org/rimian/{size}/120658_2.png","created_at":"2017-02-13T02:48:10.177Z","like_count":0,"blurb":"What's the best way to (QUnit) assert an element on the page has some content in it? This passes: ok($.trim($('.foo').text()) == 'bar', 'content bar renders on page'); But isn't very practical. Is the...","post_number":1,"topic_title_headline":"Acceptance test content is present on page","topic_id":57292},{"id":1432567,"name":"Ayke","username":"rrit","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/r/b5ac83/{size}.png","created_at":"2024-01-09T16:53:57.916Z","like_count":0,"blurb":"Right now Discourse on meta.discourse.org serves the mobile-view instead of the crawler-view to the https://search.google.com/test/rich-results Google Rich Results Test . As there is no Schema Markup ...","post_number":1,"topic_title_headline":"Google Search Console/Schema Markup test tool “Google Rich Results Test”: mobile-view instead of crawler-view","topic_id":291039},{"id":1305996,"name":"Larry Diehl","username":"larrytheliquid","avatar_template":"/user_avatar/meta.discourse.org/larrytheliquid/{size}/310576_2.png","created_at":"2023-06-08T22:35:51.914Z","like_count":1,"blurb":"Hi Discourse Community! :slight_smile: I've been working on tech ( https://colimit.io Colimit ) that helps people apply https://en.wikipedia.org/wiki/Model-based_testing Model-based testing to test th...","post_number":1,"topic_title_headline":"Experiments with Model-Based Testing","topic_id":267737},{"id":1135474,"name":"Robert","username":"merefield","avatar_template":"/user_avatar/meta.discourse.org/merefield/{size}/176214_2.png","created_at":"2022-07-20T08:48:28.868Z","like_count":0,"blurb":"...my case running: rake \"plugin:qunit[discourse-multilingual]\" with a branch installed. I'm declaring a function in my initializer (i'm extending I18n ) The tests sometimes (25%?) seem to run before the...","post_number":1,"topic_title_headline":"Qunit tests not deterministic in Plugin?","topic_id":233389},{"id":672801,"name":"Jay Pfaffman","username":"pfaffman","avatar_template":"/user_avatar/meta.discourse.org/pfaffman/{size}/120154_2.png","created_at":"2019-12-12T21:01:31.303Z","like_count":0,"blurb":"Thanks, @Mittineague ! After a while, that made sense. I even wrote a spec, but even before I added my spec (and when I reverted to before I added any code), specs fail because: An error occurred whil...","post_number":1,"topic_title_headline":"Issues migrating test database","topic_id":135876},{"id":871105,"name":"","username":"Alteras","avatar_template":"/user_avatar/meta.discourse.org/alteras/{size}/179824_2.png","created_at":"2021-01-07T19:56:05.071Z","like_count":4,"blurb":"Hello! I'm currently working on a Markdown Extension/plugin that adds quite a number of BBCode tags, and I am looking to write QUnit Acceptance tests for them (I got really tired of constantly checkin...","post_number":1,"topic_title_headline":"Acceptance Test for Markdown Extension?","topic_id":175413},{"id":747613,"name":"","username":"xrav3nz","avatar_template":"/user_avatar/meta.discourse.org/xrav3nz/{size}/76894_2.png","created_at":"2020-05-08T04:27:56.920Z","like_count":8,"blurb":"...development - #2 by taylorthurlow - A May Of WTFs - Ruby on Rails Discussions Not sure if we have explored this before, but Rails can automatically maintain test databse schema with ActiveRecord::Migr...","post_number":1,"topic_title_headline":"Auto migrate test database schema","topic_id":150786},{"id":583961,"name":"Kim Miller","username":"kimardenmiller","avatar_template":"/user_avatar/meta.discourse.org/kimardenmiller/{size}/119631_2.png","created_at":"2019-05-24T22:21:24.996Z","like_count":3,"blurb":"Adding some polls API endpoints for PR to discourse_api, which work fine. Now I'm trying to understand how to create tests before submitting the PR, e.g.: require 'spec_helper' describe DiscourseApi::...","post_number":1,"topic_title_headline":"Building Tests for New discourse_api Endpoints","topic_id":118639},{"id":621707,"name":"Andrew Lank","username":"alank","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/a/c89c15/{size}.png","created_at":"2019-08-17T02:13:06.679Z","like_count":1,"blurb":"In my development and testing I'm running a Discourse instance (Docker Discourse from Bitnami) and it fulfills most of my API testing for our API service which talks to Discourse, however I now need t...","post_number":1,"topic_title_headline":"Seed or API calls to create test users","topic_id":126025},{"id":334186,"name":"Chris","username":"ChrisBeach","avatar_template":"/user_avatar/meta.discourse.org/chrisbeach/{size}/214628_2.png","created_at":"2017-10-01T08:18:48.148Z","like_count":1,"blurb":"...from the core team. I propose that on hitting the upgrade button, a new docker image is built in the background, and within it, acceptance tests of all plugins are run before the switch-over happens f...","post_number":1,"topic_title_headline":"Smoke-testing plugins during upgrade process","topic_id":71118},{"id":288991,"name":"David Taylor","username":"david","avatar_template":"/user_avatar/meta.discourse.org/david/{size}/157490_2.png","created_at":"2017-05-16T10:28:58.496Z","like_count":1,"blurb":"I'm trying to use the docker image for tests both on my mac, and also https://meta.discourse.org/t/setting-up-plugin-continuous-integration-tests-on-travis-ci/59612 on travis . For a while now the qun...","post_number":1,"topic_title_headline":"QUnit tests won’t pass in discourse_dev docker image","topic_id":62797},{"id":187533,"name":"Sckott","username":"sckott","avatar_template":"/user_avatar/meta.discourse.org/sckott/{size}/115359_2.png","created_at":"2016-04-21T19:15:00.191Z","like_count":0,"blurb":"What's the best or fastest way to get Discourse installed on Travis for testing a client for the Discourse API ? It appears as though the discourse_api gem uses webmock so I think does not use a real ...","post_number":1,"topic_title_headline":"Testing a Discourse API client on Travis-CI","topic_id":42947},{"id":966756,"name":"Connor Parrish","username":"Connor_Parrish","avatar_template":"/user_avatar/meta.discourse.org/connor_parrish/{size}/225463_2.png","created_at":"2021-07-22T16:57:05.885Z","like_count":1,"blurb":"When you're conditionally adding a PostMenuButton using the plugin-api , the extra button is included in _extraButtons in between acceptance tests. When I run tests, if the tests where the button shou...","post_number":1,"topic_title_headline":"PostMenu’s ‘_extraButtons’ isn’t reset in between acceptance tests","topic_id":197887}],"topics":[{"id":49167,"title":"Write acceptance tests and component tests for Ember code in Discourse","fancy_title":"Write acceptance tests and component tests for Ember code in Discourse","slug":"write-acceptance-tests-and-component-tests-for-ember-code-in-discourse","posts_count":3,"reply_count":1,"highest_post_number":3,"created_at":"2016-08-24T20:48:02.492Z","last_posted_at":"2017-02-01T18:22:01.859Z","bumped":true,"bumped_at":"2017-02-01T18:22:01.859Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["tutorial","ember","testing"],"tags_descriptions":{},"category_id":56,"has_accepted_answer":false},{"id":32619,"title":"Developing Discourse Plugins - Part 6 - Add acceptance tests","fancy_title":"Developing Discourse Plugins - Part 6 - Add acceptance tests","slug":"developing-discourse-plugins-part-6-add-acceptance-tests","posts_count":33,"reply_count":26,"highest_post_number":38,"created_at":"2015-08-27T21:32:26.323Z","last_posted_at":"2022-06-02T11:06:38.274Z","bumped":true,"bumped_at":"2022-06-02T11:06:38.274Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["plugins","tutorial","plugin-guides","testing"],"tags_descriptions":{"plugins":""},"category_id":56,"has_accepted_answer":false},{"id":281579,"title":"End-to-end system testing for themes and theme components","fancy_title":"End-to-end system testing for themes and theme components","slug":"end-to-end-system-testing-for-themes-and-theme-components","posts_count":1,"reply_count":0,"highest_post_number":1,"created_at":"2023-10-24T23:13:37.118Z","last_posted_at":"2023-10-24T23:13:37.118Z","bumped":true,"bumped_at":"2023-11-13T23:20:01.596Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["how-to","themes"],"tags_descriptions":{"how-to":"How to guides contain steps to follow to solve a specific problem"},"category_id":56,"has_accepted_answer":false},{"id":66857,"title":"How to run Discourse core, plugin and theme QUnit test suites","fancy_title":"How to run Discourse core, plugin and theme QUnit test suites","slug":"how-to-run-discourse-core-plugin-and-theme-qunit-test-suites","posts_count":1,"reply_count":2,"highest_post_number":1,"created_at":"2017-07-26T14:09:58.032Z","last_posted_at":"2017-07-26T14:09:58.126Z","bumped":true,"bumped_at":"2023-09-04T17:56:33.079Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["how-to"],"tags_descriptions":{"how-to":"How to guides contain steps to follow to solve a specific problem"},"category_id":56,"has_accepted_answer":false},{"id":17155,"title":"Test Discourse in mobile screen emulator","fancy_title":"Test Discourse in mobile screen emulator","slug":"test-discourse-in-mobile-screen-emulator","posts_count":3,"reply_count":1,"highest_post_number":3,"created_at":"2014-07-03T11:45:09.360Z","last_posted_at":"2014-10-12T22:19:24.137Z","bumped":true,"bumped_at":"2014-10-12T22:19:24.137Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["how-to"],"tags_descriptions":{"how-to":"How to guides contain steps to follow to solve a specific problem"},"category_id":56,"has_accepted_answer":false},{"id":288363,"title":"Tip: when testing inbound email with fake user accounts...","fancy_title":"Tip: when testing inbound email with fake user accounts…","slug":"tip-when-testing-inbound-email-with-fake-user-accounts","posts_count":1,"reply_count":0,"highest_post_number":1,"created_at":"2023-12-12T10:51:07.868Z","last_posted_at":"2023-12-12T10:51:08.401Z","bumped":true,"bumped_at":"2023-12-12T10:51:08.401Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["email"],"tags_descriptions":{},"category_id":55,"has_accepted_answer":false},{"id":145744,"title":"Generate User API Keys for testing","fancy_title":"Generate User API Keys for testing","slug":"generate-user-api-keys-for-testing","posts_count":4,"reply_count":1,"highest_post_number":4,"created_at":"2020-03-26T21:31:38.269Z","last_posted_at":"2022-08-07T15:27:18.788Z","bumped":true,"bumped_at":"2022-08-07T15:27:18.788Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["how-to"],"tags_descriptions":{"how-to":"How to guides contain steps to follow to solve a specific problem"},"category_id":56,"has_accepted_answer":false},{"id":58298,"title":"Build a sandbox to test changes before making them live","fancy_title":"Build a sandbox to test changes before making them live","slug":"build-a-sandbox-to-test-changes-before-making-them-live","posts_count":21,"reply_count":13,"highest_post_number":21,"created_at":"2017-03-03T13:34:07.921Z","last_posted_at":"2022-02-16T23:18:54.680Z","bumped":true,"bumped_at":"2022-02-16T23:18:54.680Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["how-to"],"tags_descriptions":{"how-to":"How to guides contain steps to follow to solve a specific problem"},"category_id":55,"has_accepted_answer":false},{"id":118296,"title":"Migrate from tests-passed to stable","fancy_title":"Migrate from tests-passed to stable","slug":"migrate-from-tests-passed-to-stable","posts_count":12,"reply_count":7,"highest_post_number":12,"created_at":"2019-05-21T10:42:51.017Z","last_posted_at":"2019-06-21T15:55:53.072Z","bumped":true,"bumped_at":"2019-05-22T15:55:47.651Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":288705,"title":"502 Bad Gateway after online rebuild of tests-passed Production just now","fancy_title":"502 Bad Gateway after online rebuild of tests-passed Production just now","slug":"502-bad-gateway-after-online-rebuild-of-tests-passed-production-just-now","posts_count":6,"reply_count":1,"highest_post_number":6,"created_at":"2023-12-14T17:11:26.387Z","last_posted_at":"2024-01-13T17:37:27.535Z","bumped":true,"bumped_at":"2023-12-14T17:36:57.066Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":247546,"title":"Trouble on adding a simple unit test for Youtube oneboxing","fancy_title":"Trouble on adding a simple unit test for Youtube oneboxing","slug":"trouble-on-adding-a-simple-unit-test-for-youtube-oneboxing","posts_count":10,"reply_count":5,"highest_post_number":10,"created_at":"2022-12-02T20:49:55.713Z","last_posted_at":"2023-01-05T17:47:40.629Z","bumped":true,"bumped_at":"2022-12-06T17:47:28.771Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":["onebox","testing"],"tags_descriptions":{},"category_id":7,"has_accepted_answer":true},{"id":274165,"title":"Strange QUnit behaviour?: test failing because setting value doesn't survive","fancy_title":"Strange QUnit behaviour?: test failing because setting value doesn’t survive","slug":"strange-qunit-behaviour-test-failing-because-setting-value-doesnt-survive","posts_count":5,"reply_count":2,"highest_post_number":5,"created_at":"2023-08-07T10:05:22.298Z","last_posted_at":"2023-09-06T11:19:24.509Z","bumped":true,"bumped_at":"2023-08-07T11:24:27.150Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":true},{"id":249167,"title":"Is it possible to override the Site object with own fixture during Front End tests of a Plugin?","fancy_title":"Is it possible to override the Site object with own fixture during Front End tests of a Plugin?","slug":"is-it-possible-to-override-the-site-object-with-own-fixture-during-front-end-tests-of-a-plugin","posts_count":4,"reply_count":0,"highest_post_number":4,"created_at":"2022-12-16T18:32:29.079Z","last_posted_at":"2022-12-28T21:57:56.070Z","bumped":true,"bumped_at":"2022-12-28T21:57:56.070Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":true},{"id":286355,"title":"Acceptance tests failing on Github Actions","fancy_title":"Acceptance tests failing on Github Actions","slug":"acceptance-tests-failing-on-github-actions","posts_count":6,"reply_count":0,"highest_post_number":6,"created_at":"2023-11-22T18:46:46.128Z","last_posted_at":"2023-11-24T13:59:40.100Z","bumped":true,"bumped_at":"2023-11-24T13:59:40.100Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":true},{"id":91312,"title":"There was a problem sending the test email","fancy_title":"There was a problem sending the test email","slug":"there-was-a-problem-sending-the-test-email","posts_count":8,"reply_count":5,"highest_post_number":8,"created_at":"2018-07-01T17:12:48.366Z","last_posted_at":"2018-08-01T09:28:21.935Z","bumped":true,"bumped_at":"2018-07-02T09:28:16.014Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":287022,"title":"Strange migration error in tests during GH workflow","fancy_title":"Strange migration error in tests during GH workflow","slug":"strange-migration-error-in-tests-during-gh-workflow","posts_count":6,"reply_count":3,"highest_post_number":6,"created_at":"2023-11-29T23:18:11.686Z","last_posted_at":"2023-12-31T15:09:05.633Z","bumped":true,"bumped_at":"2023-12-01T15:08:31.257Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":true},{"id":238372,"title":"Smtp doctor test using port 465 even though its configured to use 2525","fancy_title":"Smtp doctor test using port 465 even though its configured to use 2525","slug":"smtp-doctor-test-using-port-465-even-though-its-configured-to-use-2525","posts_count":12,"reply_count":7,"highest_post_number":12,"created_at":"2022-09-07T20:22:10.976Z","last_posted_at":"2022-10-09T00:21:55.656Z","bumped":true,"bumped_at":"2022-09-09T00:21:48.272Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":146326,"title":"Sidekiq not running. Sidekiq heartbeat test failed, restarting","fancy_title":"Sidekiq not running. Sidekiq heartbeat test failed, restarting","slug":"sidekiq-not-running-sidekiq-heartbeat-test-failed-restarting","posts_count":16,"reply_count":9,"highest_post_number":16,"created_at":"2020-03-31T18:51:44.061Z","last_posted_at":"2020-06-10T01:39:28.366Z","bumped":true,"bumped_at":"2020-05-11T01:39:26.054Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":["unsupported-install"],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":255406,"title":"Tests for plugin that requires a plugin","fancy_title":"Tests for plugin that requires a plugin","slug":"tests-for-plugin-that-requires-a-plugin","posts_count":3,"reply_count":0,"highest_post_number":3,"created_at":"2023-02-16T22:12:10.344Z","last_posted_at":"2023-02-28T16:10:36.498Z","bumped":true,"bumped_at":"2023-02-28T20:21:06.122Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":true},{"id":246069,"title":"Need to restart Ember in order to test front-end changes","fancy_title":"Need to restart Ember in order to test front-end changes","slug":"need-to-restart-ember-in-order-to-test-front-end-changes","posts_count":5,"reply_count":1,"highest_post_number":5,"created_at":"2022-11-18T17:03:45.900Z","last_posted_at":"2022-11-18T18:17:53.648Z","bumped":true,"bumped_at":"2022-11-18T18:17:53.648Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":true},{"id":227090,"title":"Bitnami Discourse VM on Virtualbox + SMTP mail-server for testing","fancy_title":"Bitnami Discourse VM on Virtualbox + SMTP mail-server for testing","slug":"bitnami-discourse-vm-on-virtualbox-smtp-mail-server-for-testing","posts_count":3,"reply_count":0,"highest_post_number":3,"created_at":"2022-05-15T18:02:33.136Z","last_posted_at":"2022-05-16T09:10:43.794Z","bumped":true,"bumped_at":"2022-05-16T09:10:43.794Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":["unsupported-install"],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":145781,"title":"Test emails sent but","fancy_title":"Test emails sent but","slug":"test-emails-sent-but","posts_count":5,"reply_count":2,"highest_post_number":5,"created_at":"2020-03-27T07:14:10.116Z","last_posted_at":"2020-04-29T02:52:31.927Z","bumped":true,"bumped_at":"2020-03-30T02:52:29.062Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":137496,"title":"Sidekiq heartbeat test failed, restarting","fancy_title":"Sidekiq heartbeat test failed, restarting","slug":"sidekiq-heartbeat-test-failed-restarting","posts_count":13,"reply_count":8,"highest_post_number":13,"created_at":"2020-01-01T11:20:33.492Z","last_posted_at":"2020-02-11T23:09:42.375Z","bumped":true,"bumped_at":"2020-01-12T23:09:39.730Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":212684,"title":"MailGun & Discourse: Sandbox subdomains are for test purposes only. Please add your own domain","fancy_title":"MailGun & Discourse: Sandbox subdomains are for test purposes only. Please add your own domain","slug":"mailgun-discourse-sandbox-subdomains-are-for-test-purposes-only-please-add-your-own-domain","posts_count":4,"reply_count":2,"highest_post_number":5,"created_at":"2021-12-20T13:52:10.405Z","last_posted_at":"2022-01-19T15:27:11.368Z","bumped":true,"bumped_at":"2021-12-20T15:26:24.911Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":102035,"title":"Test admin features without having to install Discourse","fancy_title":"Test admin features without having to install Discourse","slug":"test-admin-features-without-having-to-install-discourse","posts_count":6,"reply_count":2,"highest_post_number":6,"created_at":"2018-11-14T15:20:59.484Z","last_posted_at":"2021-09-09T07:35:18.270Z","bumped":true,"bumped_at":"2021-09-09T07:35:18.270Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":121299,"title":"Install discourse with a staging (test) ssl certificate","fancy_title":"Install discourse with a staging (test) ssl certificate","slug":"install-discourse-with-a-staging-test-ssl-certificate","posts_count":5,"reply_count":1,"highest_post_number":5,"created_at":"2019-06-25T17:12:34.552Z","last_posted_at":"2023-04-01T03:25:32.925Z","bumped":true,"bumped_at":"2019-09-04T15:15:31.153Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":118568,"title":"Problem testing Badge Query from Data Explorer","fancy_title":"Problem testing Badge Query from Data Explorer","slug":"problem-testing-badge-query-from-data-explorer","posts_count":5,"reply_count":1,"highest_post_number":6,"created_at":"2019-05-24T00:14:35.814Z","last_posted_at":"2019-05-24T00:46:42.560Z","bumped":true,"bumped_at":"2019-05-24T00:46:42.560Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":["sql-triggered-badge"],"tags_descriptions":{"sql-triggered-badge":"SQL queries for custom triggered badges"},"category_id":148,"has_accepted_answer":true},{"id":115912,"title":"New iOS mobile app beta available for testing","fancy_title":"New iOS mobile app beta available for testing","slug":"new-ios-mobile-app-beta-available-for-testing","posts_count":49,"reply_count":31,"highest_post_number":49,"created_at":"2019-04-25T01:24:56.608Z","last_posted_at":"2019-05-31T17:31:02.516Z","bumped":true,"bumped_at":"2020-01-21T17:07:37.817Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":224560,"title":"502 Bad Gateway after trying to rebuild test-passed branch","fancy_title":"502 Bad Gateway after trying to rebuild test-passed branch","slug":"502-bad-gateway-after-trying-to-rebuild-test-passed-branch","posts_count":6,"reply_count":1,"highest_post_number":6,"created_at":"2022-04-17T21:46:04.598Z","last_posted_at":"2022-04-17T22:15:37.255Z","bumped":true,"bumped_at":"2022-04-17T22:15:37.255Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":false},{"id":173019,"title":"Forum offline due to failed rebuilds on Tests-Pass","fancy_title":"Forum offline due to failed rebuilds on Tests-Pass","slug":"forum-offline-due-to-failed-rebuilds-on-tests-pass","posts_count":2,"reply_count":0,"highest_post_number":2,"created_at":"2020-12-11T18:44:17.952Z","last_posted_at":"2020-12-11T19:12:11.141Z","bumped":true,"bumped_at":"2020-12-11T19:12:11.141Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":false},{"id":196501,"title":"[A/B Testing] Changing parent CSS class based on experiment variable","fancy_title":"[A/B Testing] Changing parent CSS class based on experiment variable","slug":"a-b-testing-changing-parent-css-class-based-on-experiment-variable","posts_count":3,"reply_count":1,"highest_post_number":3,"created_at":"2021-07-08T19:58:55.503Z","last_posted_at":"2021-07-15T18:19:21.092Z","bumped":true,"bumped_at":"2021-07-15T18:19:21.092Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":282856,"title":"Code-level performance testing","fancy_title":"Code-level performance testing","slug":"code-level-performance-testing","posts_count":2,"reply_count":0,"highest_post_number":2,"created_at":"2023-10-20T03:44:49.568Z","last_posted_at":"2023-10-23T23:29:59.741Z","bumped":true,"bumped_at":"2023-10-23T23:29:59.741Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":272112,"title":"Running core tests in docker environment","fancy_title":"Running core tests in docker environment","slug":"running-core-tests-in-docker-environment","posts_count":1,"reply_count":0,"highest_post_number":1,"created_at":"2023-07-19T12:45:57.266Z","last_posted_at":"2023-07-19T12:45:57.446Z","bumped":true,"bumped_at":"2023-07-19T12:45:57.446Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["docker","spec","testing"],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":87295,"title":"Email test from the console?","fancy_title":"Email test from the console?","slug":"email-test-from-the-console","posts_count":4,"reply_count":1,"highest_post_number":4,"created_at":"2018-05-11T22:03:23.101Z","last_posted_at":"2018-05-12T00:55:41.843Z","bumped":true,"bumped_at":"2018-05-12T00:55:41.843Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":59577,"title":"Plugin QUnit tests are not running as part of rake qunit:test","fancy_title":"Plugin QUnit tests are not running as part of rake qunit:test","slug":"plugin-qunit-tests-are-not-running-as-part-of-rake-qunit-test","posts_count":8,"reply_count":5,"highest_post_number":8,"created_at":"2017-03-20T20:29:06.536Z","last_posted_at":"2017-07-17T18:26:45.188Z","bumped":true,"bumped_at":"2017-07-17T18:26:45.188Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":181856,"title":"How does the Discourse team do usability testing?","fancy_title":"How does the Discourse team do usability testing?","slug":"how-does-the-discourse-team-do-usability-testing","posts_count":5,"reply_count":2,"highest_post_number":6,"created_at":"2021-03-03T18:09:29.357Z","last_posted_at":"2021-03-04T15:23:46.571Z","bumped":true,"bumped_at":"2021-03-04T15:23:46.571Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":3,"has_accepted_answer":false},{"id":108110,"title":"Advice on writing Ruby tests for plugins","fancy_title":"Advice on writing Ruby tests for plugins","slug":"advice-on-writing-ruby-tests-for-plugins","posts_count":15,"reply_count":13,"highest_post_number":15,"created_at":"2019-02-01T09:52:27.051Z","last_posted_at":"2019-05-03T03:54:47.163Z","bumped":true,"bumped_at":"2019-05-03T03:54:47.163Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":57292,"title":"Acceptance test content is present on page","fancy_title":"Acceptance test content is present on page","slug":"acceptance-test-content-is-present-on-page","posts_count":7,"reply_count":3,"highest_post_number":7,"created_at":"2017-02-13T02:48:10.108Z","last_posted_at":"2017-02-14T05:55:31.520Z","bumped":true,"bumped_at":"2017-02-14T05:55:31.520Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":291039,"title":"Google Search Console/Schema Markup test tool \"Google Rich Results Test\": mobile-view instead of crawler-view","fancy_title":"Google Search Console/Schema Markup test tool “Google Rich Results Test”: mobile-view instead of crawler-view","slug":"google-search-console-schema-markup-test-tool-google-rich-results-test-mobile-view-instead-of-crawler-view","posts_count":3,"reply_count":1,"highest_post_number":3,"created_at":"2024-01-09T16:53:57.797Z","last_posted_at":"2024-01-09T17:17:09.039Z","bumped":true,"bumped_at":"2024-01-09T17:17:09.039Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":1,"has_accepted_answer":false},{"id":267737,"title":"Experiments with Model-Based Testing","fancy_title":"Experiments with Model-Based Testing","slug":"experiments-with-model-based-testing","posts_count":1,"reply_count":0,"highest_post_number":1,"created_at":"2023-06-08T22:35:51.784Z","last_posted_at":"2023-06-08T22:35:51.914Z","bumped":true,"bumped_at":"2023-06-08T22:35:51.914Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":233389,"title":"Qunit tests not deterministic in Plugin?","fancy_title":"Qunit tests not deterministic in Plugin?","slug":"qunit-tests-not-deterministic-in-plugin","posts_count":2,"reply_count":0,"highest_post_number":2,"created_at":"2022-07-20T08:48:28.757Z","last_posted_at":"2022-07-20T11:03:42.522Z","bumped":true,"bumped_at":"2022-07-20T11:03:42.522Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":135876,"title":"Issues migrating test database","fancy_title":"Issues migrating test database","slug":"issues-migrating-test-database","posts_count":9,"reply_count":1,"highest_post_number":9,"created_at":"2019-12-12T21:01:31.303Z","last_posted_at":"2021-03-02T16:44:20.120Z","bumped":true,"bumped_at":"2021-03-02T16:44:20.120Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":175413,"title":"Acceptance Test for Markdown Extension?","fancy_title":"Acceptance Test for Markdown Extension?","slug":"acceptance-test-for-markdown-extension","posts_count":3,"reply_count":0,"highest_post_number":4,"created_at":"2021-01-07T19:56:04.931Z","last_posted_at":"2021-01-10T11:26:16.888Z","bumped":true,"bumped_at":"2021-01-10T16:30:56.164Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":150786,"title":"Auto migrate test database schema","fancy_title":"Auto migrate test database schema","slug":"auto-migrate-test-database-schema","posts_count":2,"reply_count":0,"highest_post_number":2,"created_at":"2020-05-08T04:27:56.739Z","last_posted_at":"2020-05-08T13:15:45.247Z","bumped":true,"bumped_at":"2020-05-08T13:15:45.247Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":118639,"title":"Building Tests for New discourse_api Endpoints","fancy_title":"Building Tests for New discourse_api Endpoints","slug":"building-tests-for-new-discourse-api-endpoints","posts_count":20,"reply_count":9,"highest_post_number":21,"created_at":"2019-05-24T22:21:24.896Z","last_posted_at":"2019-10-02T21:13:35.140Z","bumped":true,"bumped_at":"2019-10-02T21:36:26.639Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":126025,"title":"Seed or API calls to create test users","fancy_title":"Seed or API calls to create test users","slug":"seed-or-api-calls-to-create-test-users","posts_count":7,"reply_count":5,"highest_post_number":7,"created_at":"2019-08-17T02:13:06.578Z","last_posted_at":"2019-08-19T11:50:36.137Z","bumped":true,"bumped_at":"2019-08-19T11:50:36.137Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":71118,"title":"Smoke-testing plugins during upgrade process","fancy_title":"Smoke-testing plugins during upgrade process","slug":"smoke-testing-plugins-during-upgrade-process","posts_count":22,"reply_count":17,"highest_post_number":22,"created_at":"2017-10-01T08:18:48.067Z","last_posted_at":"2017-10-02T23:44:59.852Z","bumped":true,"bumped_at":"2017-10-02T23:44:59.852Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["pr-welcome"],"tags_descriptions":{"pr-welcome":"You're welcome to submit a Github pull request that implements this"},"category_id":2,"has_accepted_answer":false},{"id":62797,"title":"QUnit tests won't pass in discourse_dev docker image","fancy_title":"QUnit tests won’t pass in discourse_dev docker image","slug":"qunit-tests-wont-pass-in-discourse-dev-docker-image","posts_count":20,"reply_count":11,"highest_post_number":21,"created_at":"2017-05-16T10:28:58.397Z","last_posted_at":"2017-07-25T15:50:27.716Z","bumped":true,"bumped_at":"2017-07-25T15:50:27.716Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":42947,"title":"Testing a Discourse API client on Travis-CI","fancy_title":"Testing a Discourse API client on Travis-CI","slug":"testing-a-discourse-api-client-on-travis-ci","posts_count":5,"reply_count":3,"highest_post_number":5,"created_at":"2016-04-21T19:15:00.130Z","last_posted_at":"2016-04-22T03:03:47.976Z","bumped":true,"bumped_at":"2016-04-22T03:03:47.976Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":197887,"title":"PostMenu's '_extraButtons' isn't reset in between acceptance tests","fancy_title":"PostMenu’s ‘_extraButtons’ isn’t reset in between acceptance tests","slug":"postmenus-extrabuttons-isnt-reset-in-between-acceptance-tests","posts_count":2,"reply_count":0,"highest_post_number":4,"created_at":"2021-07-22T16:57:05.803Z","last_posted_at":"2021-08-04T09:11:29.538Z","bumped":true,"bumped_at":"2021-08-04T09:11:29.538Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false}],"users":[],"categories":[],"tags":[],"groups":[],"grouped_search_result":{"more_posts":null,"more_users":null,"more_categories":null,"term":"testing","search_log_id":2089836,"more_full_page_results":true,"can_create_topic":false,"error":null,"post_ids":[218354,138484,1381521,311252,59431,1419473,722424,266441,582008,1421414,1204506,1339808,1211823,1408389,443129,1412219,1160803,725228,1240758,1197679,1103913,722608,679950,1033333,498559,596557,583541,569209,1090520,860593,960683,1389188,1329017,421687,272436,899205,530438,260691,1432567,1305996,1135474,672801,871105,747613,583961,621707,334186,288991,187533,966756],"user_ids":[],"category_ids":[],"tag_ids":[],"group_ids":[]}} \ No newline at end of file +{"posts":[{"id":218354,"name":"Robin Ward","username":"eviltrout","avatar_template":"/user_avatar/meta.discourse.org/eviltrout/{size}/5275_2.png","created_at":"2016-08-24T20:48:02.587Z","like_count":15,"blurb":"Automated tests are a great way to protect your code against future regressions. Many people are familiar with how to do this in our Rails codebase with http://rspec.info/ rspec , but the Javascript s...","post_number":1,"topic_title_headline":"Write acceptance tests and component tests for Ember code in Discourse","topic_id":49167},{"id":138484,"name":"Robin Ward","username":"eviltrout","avatar_template":"/user_avatar/meta.discourse.org/eviltrout/{size}/5275_2.png","created_at":"2015-08-27T21:32:26.407Z","like_count":29,"blurb":"Previous tutorial: https://meta.discourse.org/t/developing-discourse-plugins-part-5-add-an-admin-interface/31761 Developing Discourse Plugins - Part 5 - Add an admin interface Did you know that Discou...","post_number":1,"topic_title_headline":"Developing Discourse Plugins - Part 6 - Add acceptance tests","topic_id":32619},{"id":1381521,"name":"Alan Tan","username":"tgxworld","avatar_template":"/user_avatar/meta.discourse.org/tgxworld/{size}/106117_2.png","created_at":"2023-10-24T23:13:37.118Z","like_count":16,"blurb":"Writing automated tests for themes is an important part of the theme development process which can help ensure that the features being introduced by a theme continues to work well overtime with core D...","post_number":1,"topic_title_headline":"End-to-end system testing for themes and theme components","topic_id":281579},{"id":311252,"name":"David Taylor","username":"david","avatar_template":"/user_avatar/meta.discourse.org/david/{size}/157490_2.png","created_at":"2017-07-26T14:09:58.126Z","like_count":17,"blurb":"Discourse has extensive frontend tests for core, plugins and themes. Once you have a functioning local development environment, those tests can be run locally in a number of different ways. Running te...","post_number":1,"topic_title_headline":"How to run Discourse core, plugin and theme QUnit test suites","topic_id":66857},{"id":59431,"name":"Erlend Sogge Heggen","username":"erlend_sh","avatar_template":"/user_avatar/meta.discourse.org/erlend_sh/{size}/119475_2.png","created_at":"2014-07-03T11:45:09.463Z","like_count":5,"blurb":"...view=1 to an URL, but the emulator has the added benefit of letting you select the screen profile of a specific device. I also tested all of the most popular online screen emulators, but unfortunately...","post_number":1,"topic_title_headline":"Test Discourse in mobile screen emulator","topic_id":17155},{"id":1419473,"name":"","username":"ToddZ","avatar_template":"/user_avatar/meta.discourse.org/toddz/{size}/328350_2.png","created_at":"2023-12-12T10:51:08.401Z","like_count":2,"blurb":"...amount of time troubleshooting inbound email because Discourse was rejecting every reply-by-email from my fake users. It had worked fine when I first tested several weeks ago… I finally realized that ...","post_number":1,"topic_title_headline":"Tip: when testing inbound email with fake user accounts…","topic_id":288363},{"id":722424,"name":"Falco","username":"Falco","avatar_template":"/user_avatar/meta.discourse.org/falco/{size}/179432_2.png","created_at":"2020-03-26T21:31:38.463Z","like_count":17,"blurb":"Continuing the discussion from https://meta.discourse.org/t/user-api-keys-specification/48536 User API keys specification : I created a small utility script in order to test User API keys locally. Fir...","post_number":1,"topic_title_headline":"Generate User API Keys for testing","topic_id":145744},{"id":266441,"name":"Andrew Waugh","username":"JagWaugh","avatar_template":"/user_avatar/meta.discourse.org/jagwaugh/{size}/69335_2.png","created_at":"2017-03-03T13:34:08.009Z","like_count":19,"blurb":"Regardless of if you're a moderator or an admin, you will no doubt at some time think about making some change to your live site and wonder if this will bring shame on you, and/or cause yourself an en...","post_number":1,"topic_title_headline":"Build a sandbox to test changes before making them live","topic_id":58298},{"id":582008,"name":"","username":"Wurzelseppi","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/w/eada6e/{size}.png","created_at":"2019-05-21T10:42:51.099Z","like_count":0,"blurb":"Hi guys, just wanted to migrate from 2.3.0beta9 to stable release and got this error: What can I do here ? Caused by: PG::UndefinedColumn: ERROR: column \"email_private_messages\" of relation \"user_opti...","post_number":1,"topic_title_headline":"Migrate from tests-passed to stable","topic_id":118296},{"id":1421414,"name":"Robert","username":"merefield","avatar_template":"/user_avatar/meta.discourse.org/merefield/{size}/176214_2.png","created_at":"2023-12-14T17:11:26.575Z","like_count":1,"blurb":"Restarting the server, restarting the container, console rebuilding doesn't help. Container is up as I can ./launcher enter app Got a bunch of these in logs, ideas on how to investigate redis failure?...","post_number":1,"topic_title_headline":"502 Bad Gateway after online rebuild of tests-passed Production just now","topic_id":288705},{"id":1204506,"name":"Coin-coin le Canapin","username":"Canapin","avatar_template":"/user_avatar/meta.discourse.org/canapin/{size}/119591_2.png","created_at":"2022-12-02T20:49:55.867Z","like_count":1,"blurb":"Hi! I want to add support of /shorts/ Youtube link. My modification of the YoutubeOnebox class works, but it is required that I add a test in https://github.com/discourse/discourse/blob/493d437e79f88a...","post_number":1,"topic_title_headline":"Trouble on adding a simple unit test for Youtube oneboxing","topic_id":247546},{"id":1339808,"name":"Robert","username":"merefield","avatar_template":"/user_avatar/meta.discourse.org/merefield/{size}/176214_2.png","created_at":"2023-08-07T10:05:22.444Z","like_count":2,"blurb":"I have a strange issue with QUnit. This test is extremely simple and should be straightforward … but … A plugin setting is changing from those I have set up. https://github.com/paviliondev/discourse-l...","post_number":1,"topic_title_headline":"Strange QUnit behaviour?: test failing because setting value doesn’t survive","topic_id":274165},{"id":1211823,"name":"Robert","username":"merefield","avatar_template":"/user_avatar/meta.discourse.org/merefield/{size}/176214_2.png","created_at":"2022-12-16T18:32:29.231Z","like_count":1,"blurb":"...presenting formatted location on User Card by merefield · Pull Request #73 · paviliondev/discourse-locations · GitHub I'm attempting to cover the change with a new Front End test. But the test fails t...","post_number":1,"topic_title_headline":"Is it possible to override the Site object with own fixture during Front End tests of a Plugin?","topic_id":249167},{"id":1408389,"name":"Pierre Romera","username":"pirhoo","avatar_template":"/user_avatar/meta.discourse.org/pirhoo/{size}/120058_2.png","created_at":"2023-11-22T18:46:46.469Z","like_count":5,"blurb":"...m setting up a new plugin based on the https://github.com/discourse/discourse-plugin-skeleton/tree/main/assets skeleton you provided which already helped me a lot. I am now writing tests, both for the...","post_number":1,"topic_title_headline":"Acceptance tests failing on Github Actions","topic_id":286355},{"id":443129,"name":"JK Baseer","username":"JKBaseer","avatar_template":"/user_avatar/meta.discourse.org/jkbaseer/{size}/80471_2.png","created_at":"2018-07-01T17:12:48.446Z","like_count":0,"blurb":"...but still could myself. Background: I installed discourse using digitalocean oneclick installer. The website is running under http://forum.example.org forum.example.org without any problem except the ...","post_number":1,"topic_title_headline":"There was a problem sending the test email","topic_id":91312},{"id":1412219,"name":"Robert","username":"merefield","avatar_template":"/user_avatar/meta.discourse.org/merefield/{size}/176214_2.png","created_at":"2023-11-29T23:18:11.815Z","like_count":1,"blurb":"I'm trying to create a foreign key relationship with the Topics table. The problem is it is failing in github workflow test environment during tests for the strangest reason, it is trying to access a ...","post_number":1,"topic_title_headline":"Strange migration error in tests during GH workflow","topic_id":287022},{"id":1160803,"name":"Bryan Joseph","username":"Bryan_Joseph","avatar_template":"/user_avatar/meta.discourse.org/bryan_joseph/{size}/273248_2.png","created_at":"2022-09-07T20:22:11.191Z","like_count":1,"blurb":"...SETTINGS ==================== DISCOURSE_HOSTNAME=url SMTP_ADDRESS=smtp.mailgun.org DEVELOPER_EMAILS=REDACTED SMTP_PASSWORD=REDACTED SMTP_PORT=2525 SMTP_USER_NAME=url LETSENCRYPT_ACCOUNT_EMAIL=REDACTED...","post_number":1,"topic_title_headline":"Smtp doctor test using port 465 even though its configured to use 2525","topic_id":238372},{"id":725228,"name":"james.network","username":"sunjam","avatar_template":"/user_avatar/meta.discourse.org/sunjam/{size}/175682_2.png","created_at":"2020-03-31T18:51:44.216Z","like_count":0,"blurb":"...Redis or updating it; it hasn't really been touched in the last 8+ months. I have not agentlly dealt with Redis before, but our Tests-Pass Discourse instance was setup using https://hub.docker.com/r...","post_number":1,"topic_title_headline":"Sidekiq not running. Sidekiq heartbeat test failed, restarting","topic_id":146326},{"id":1240758,"name":"Jay Pfaffman","username":"pfaffman","avatar_template":"/user_avatar/meta.discourse.org/pfaffman/{size}/120154_2.png","created_at":"2023-02-16T22:12:10.456Z","like_count":4,"blurb":"I see that the discourse-plugin-skeleton now has this: uses: discourse/.github/.github/workflows/discourse-plugin.yml@v1 so we don't have to keep updating stuff. But I have a plugin that requires the ...","post_number":1,"topic_title_headline":"Tests for plugin that requires a plugin","topic_id":255406},{"id":1197679,"name":"","username":"SilK","avatar_template":"/user_avatar/meta.discourse.org/silk/{size}/268124_2.png","created_at":"2022-11-18T17:03:46.056Z","like_count":0,"blurb":"...a new dev environment for working on plugins. Discourse is up to date with the main branch. I need to restart Ember in order to test changes made to the front end. This includes changes to Handlebars,...","post_number":1,"topic_title_headline":"Need to restart Ember in order to test front-end changes","topic_id":246069},{"id":1103913,"name":"Banibrata Dutta","username":"bdutta","avatar_template":"/user_avatar/meta.discourse.org/bdutta/{size}/259973_2.png","created_at":"2022-05-15T18:02:33.290Z","like_count":0,"blurb":"...only in a captive host-only testbed, so wondering if there is any local network SMTP daemon / service that I could start to complete the testing ? I'm happy with 100% command line mail client and serv...","post_number":1,"topic_title_headline":"Bitnami Discourse VM on Virtualbox + SMTP mail-server for testing","topic_id":227090},{"id":722608,"name":"Lona Lee","username":"Lona_Lee","avatar_template":"/user_avatar/meta.discourse.org/lona_lee/{size}/169072_2.png","created_at":"2020-03-27T07:14:10.239Z","like_count":1,"blurb":"Hello. I'm trying to get email setup working on my discourse instance. Done set-up properly and looks fine(no errors), so sent test emails. (logs confirmed : \"Admin\" - \"Emails\" - \"Sent\") However, I ha...","post_number":1,"topic_title_headline":"Test emails sent but","topic_id":145781},{"id":679950,"name":"Oleg Bovykin","username":"arrowcircle","avatar_template":"/user_avatar/meta.discourse.org/arrowcircle/{size}/100035_2.png","created_at":"2020-01-01T11:20:33.618Z","like_count":1,"blurb":"Hi! I found strange error in my admin page, that sidekiq is not running. I opened logs and found hundreds errors like: /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/logster-2.5.1/lib/logster/logger...","post_number":1,"topic_title_headline":"Sidekiq heartbeat test failed, restarting","topic_id":137496},{"id":1033333,"name":"М. М.","username":"М_М","avatar_template":"/user_avatar/meta.discourse.org/м_м/{size}/243710_2.png","created_at":"2021-12-20T13:52:10.488Z","like_count":0,"blurb":"...user, the logs say like this Job exception: could not get 3xx (421: 421 Domain sandbox410fe5c7bb85483c941c05b4ec5f3495.mailgun.org is not allowed to send: Sandbox subdomains are for test purposes only...","post_number":1,"topic_title_headline":"MailGun & Discourse: Sandbox subdomains are for test purposes only. Please add your own domain","topic_id":212684},{"id":498559,"name":"","username":"desrocchi","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/d/eb9ed0/{size}.png","created_at":"2018-11-14T15:20:59.607Z","like_count":0,"blurb":"Is there a way for me to see or test the admin options in the demo area? I am just a moderator on the platform we use but I would like to see which options could be of use without having to install th...","post_number":1,"topic_title_headline":"Test admin features without having to install Discourse","topic_id":102035},{"id":596557,"name":"Flaviu","username":"UnivacTwo","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/u/df705f/{size}.png","created_at":"2019-06-25T17:12:34.710Z","like_count":0,"blurb":"Let's encrypt has a limit of how many certificates can be generated in a week for the same domain. Unfortunately we reach this limit and we cannot generate a new certificate this week. We did a backup...","post_number":1,"topic_title_headline":"Install discourse with a staging (test) ssl certificate","topic_id":121299},{"id":583541,"name":"mark78","username":"Mark_Schmucker","avatar_template":"/user_avatar/meta.discourse.org/mark_schmucker/{size}/124810_2.png","created_at":"2019-05-24T00:14:35.895Z","like_count":0,"blurb":"Should I be able to run any Badge Query in https://meta.discourse.org/t/32566 Data Explorer ? I want to create a custom Badge Query using \"Appreciated\" as a starting point. I type the Appreciated quer...","post_number":1,"topic_title_headline":"Problem testing Badge Query from Data Explorer","topic_id":118568},{"id":569209,"name":"Penar Musaraj","username":"pmusaraj","avatar_template":"/user_avatar/meta.discourse.org/pmusaraj/{size}/119489_2.png","created_at":"2019-04-25T01:24:56.741Z","like_count":26,"blurb":"...device and installing the app via TestFlight: https://testflight.apple.com/join/NkdBQgmg testflight.apple.com https://testflight.apple.com/join/NkdBQgmg TestFlight - Apple Using TestFlight is a great ...","post_number":1,"topic_title_headline":"New iOS mobile app beta available for testing","topic_id":115912},{"id":1090520,"name":"Mac玩儿法","username":"waerfa","avatar_template":"/user_avatar/meta.discourse.org/waerfa/{size}/216044_2.png","created_at":"2022-04-17T21:46:04.755Z","like_count":0,"blurb":"...rebuild the container: git pull ./launcher rebuild app I got the fatal error which shows: FAILED -------------------- Pups::ExecError: cd /var/www/discourse & & git fetch --depth 1 origin tests-passed...","post_number":1,"topic_title_headline":"502 Bad Gateway after trying to rebuild test-passed branch","topic_id":224560},{"id":860593,"name":"james.network","username":"sunjam","avatar_template":"/user_avatar/meta.discourse.org/sunjam/{size}/175682_2.png","created_at":"2020-12-11T18:44:18.021Z","like_count":1,"blurb":"Continuing the discussion from https://meta.discourse.org/t/postgresql-13-update/172563/27 PostgreSQL 13 update : Run into trouble while updating 2.7.0beta1 Tests-Pass in order to remove some troubles...","post_number":1,"topic_title_headline":"Forum offline due to failed rebuilds on Tests-Pass","topic_id":173019},{"id":960683,"name":"","username":"daniyal","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/d/58f4c7/{size}.png","created_at":"2021-07-08T19:58:55.634Z","like_count":3,"blurb":"...which we want to experiment. An example would be to experiment different styles of topic list view. For this we are using Google Optimize A/B testing. Currently we plan to show theme without changes t...","post_number":1,"topic_title_headline":"[A/B Testing] Changing parent CSS class based on experiment variable","topic_id":196501},{"id":1389188,"name":"Angus McLeod","username":"angus","avatar_template":"/user_avatar/meta.discourse.org/angus/{size}/341715_2.png","created_at":"2023-10-20T03:44:49.737Z","like_count":7,"blurb":"I've been looking at the performance of the https://meta.discourse.org/t/activitypub-plugin/266794 ActivityPub plugin recently and considering the best ways to reliably test, and prove, performance fo...","post_number":1,"topic_title_headline":"Code-level performance testing","topic_id":282856},{"id":1329017,"name":"","username":"dodibi","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/d/9fc348/{size}.png","created_at":"2023-07-19T12:45:57.446Z","like_count":0,"blurb":"Hello everyone! I'm currently facing some challenges while configuring my local environment to run discourse tests in a docker container. My main objective is to run the core tests with plugins attach...","post_number":1,"topic_title_headline":"Running core tests in docker environment","topic_id":272112},{"id":421687,"name":"Jay Pfaffman","username":"pfaffman","avatar_template":"/user_avatar/meta.discourse.org/pfaffman/{size}/120154_2.png","created_at":"2018-05-11T22:03:23.232Z","like_count":0,"blurb":"Is there a way to initiate an email test from the Rails console? For a zillion reasons I would love to be able to send a test email without having to create an account. I've looked in config/routes.rb...","post_number":1,"topic_title_headline":"Email test from the console?","topic_id":87295},{"id":272436,"name":"David Taylor","username":"david","avatar_template":"/user_avatar/meta.discourse.org/david/{size}/157490_2.png","created_at":"2017-03-20T20:29:06.608Z","like_count":1,"blurb":"It is my understanding that running rake qunit:test should run all of the qunit tests in Discourse, including those for any installed plugins. However, when I run the task in the docker development en...","post_number":1,"topic_title_headline":"Plugin QUnit tests are not running as part of rake qunit:test","topic_id":59577},{"id":899205,"name":"","username":"JQ331","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/j/41988e/{size}.png","created_at":"2021-03-03T18:09:29.452Z","like_count":6,"blurb":"I came across this https://blog.codinghorror.com/low-fi-usability-testing/ excellent article on how to do low-fi usability testing by @codinghorror . Usability testing (and user testing in general) is...","post_number":1,"topic_title_headline":"How does the Discourse team do usability testing?","topic_id":181856},{"id":530438,"name":"","username":"kleinfreund","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/k/a6a055/{size}.png","created_at":"2019-02-01T09:52:27.150Z","like_count":1,"blurb":"One can write tests for the backend of a plugin. For example, I created the following file in my plugin directory: spec/lib/route_store_spec.rb : require 'rails_helper' describe MyPlugin::RouteStore d...","post_number":1,"topic_title_headline":"Advice on writing Ruby tests for plugins","topic_id":108110},{"id":260691,"name":"Rimian Perkins","username":"rimian","avatar_template":"/user_avatar/meta.discourse.org/rimian/{size}/120658_2.png","created_at":"2017-02-13T02:48:10.177Z","like_count":0,"blurb":"What's the best way to (QUnit) assert an element on the page has some content in it? This passes: ok($.trim($('.foo').text()) == 'bar', 'content bar renders on page'); But isn't very practical. Is the...","post_number":1,"topic_title_headline":"Acceptance test content is present on page","topic_id":57292},{"id":1432567,"name":"Ayke","username":"rrit","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/r/b5ac83/{size}.png","created_at":"2024-01-09T16:53:57.916Z","like_count":0,"blurb":"Right now Discourse on meta.discourse.org serves the mobile-view instead of the crawler-view to the https://search.google.com/test/rich-results Google Rich Results Test . As there is no Schema Markup ...","post_number":1,"topic_title_headline":"Google Search Console/Schema Markup test tool “Google Rich Results Test”: mobile-view instead of crawler-view","topic_id":291039},{"id":1305996,"name":"Larry Diehl","username":"larrytheliquid","avatar_template":"/user_avatar/meta.discourse.org/larrytheliquid/{size}/310576_2.png","created_at":"2023-06-08T22:35:51.914Z","like_count":1,"blurb":"Hi Discourse Community! :slight_smile: I've been working on tech ( https://colimit.io Colimit ) that helps people apply https://en.wikipedia.org/wiki/Model-based_testing Model-based testing to test th...","post_number":1,"topic_title_headline":"Experiments with Model-Based Testing","topic_id":267737},{"id":1135474,"name":"Robert","username":"merefield","avatar_template":"/user_avatar/meta.discourse.org/merefield/{size}/176214_2.png","created_at":"2022-07-20T08:48:28.868Z","like_count":0,"blurb":"...my case running: rake \"plugin:qunit[discourse-multilingual]\" with a branch installed. I'm declaring a function in my initializer (i'm extending I18n ) The tests sometimes (25%?) seem to run before the...","post_number":1,"topic_title_headline":"Qunit tests not deterministic in Plugin?","topic_id":233389},{"id":672801,"name":"Jay Pfaffman","username":"pfaffman","avatar_template":"/user_avatar/meta.discourse.org/pfaffman/{size}/120154_2.png","created_at":"2019-12-12T21:01:31.303Z","like_count":0,"blurb":"Thanks, @Mittineague ! After a while, that made sense. I even wrote a spec, but even before I added my spec (and when I reverted to before I added any code), specs fail because: An error occurred whil...","post_number":1,"topic_title_headline":"Issues migrating test database","topic_id":135876},{"id":871105,"name":"","username":"Alteras","avatar_template":"/user_avatar/meta.discourse.org/alteras/{size}/179824_2.png","created_at":"2021-01-07T19:56:05.071Z","like_count":4,"blurb":"Hello! I'm currently working on a Markdown Extension/plugin that adds quite a number of BBCode tags, and I am looking to write QUnit Acceptance tests for them (I got really tired of constantly checkin...","post_number":1,"topic_title_headline":"Acceptance Test for Markdown Extension?","topic_id":175413},{"id":747613,"name":"","username":"xrav3nz","avatar_template":"/user_avatar/meta.discourse.org/xrav3nz/{size}/76894_2.png","created_at":"2020-05-08T04:27:56.920Z","like_count":8,"blurb":"...development - #2 by taylorthurlow - A May Of WTFs - Ruby on Rails Discussions Not sure if we have explored this before, but Rails can automatically maintain test databse schema with ActiveRecord::Migr...","post_number":1,"topic_title_headline":"Auto migrate test database schema","topic_id":150786},{"id":583961,"name":"Kim Miller","username":"kimardenmiller","avatar_template":"/user_avatar/meta.discourse.org/kimardenmiller/{size}/119631_2.png","created_at":"2019-05-24T22:21:24.996Z","like_count":3,"blurb":"Adding some polls API endpoints for PR to discourse_api, which work fine. Now I'm trying to understand how to create tests before submitting the PR, e.g.: require 'spec_helper' describe DiscourseApi::...","post_number":1,"topic_title_headline":"Building Tests for New discourse_api Endpoints","topic_id":118639},{"id":621707,"name":"Andrew Lank","username":"alank","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/a/c89c15/{size}.png","created_at":"2019-08-17T02:13:06.679Z","like_count":1,"blurb":"In my development and testing I'm running a Discourse instance (Docker Discourse from Bitnami) and it fulfills most of my API testing for our API service which talks to Discourse, however I now need t...","post_number":1,"topic_title_headline":"Seed or API calls to create test users","topic_id":126025},{"id":334186,"name":"Chris","username":"ChrisBeach","avatar_template":"/user_avatar/meta.discourse.org/chrisbeach/{size}/214628_2.png","created_at":"2017-10-01T08:18:48.148Z","like_count":1,"blurb":"...from the core team. I propose that on hitting the upgrade button, a new docker image is built in the background, and within it, acceptance tests of all plugins are run before the switch-over happens f...","post_number":1,"topic_title_headline":"Smoke-testing plugins during upgrade process","topic_id":71118},{"id":288991,"name":"David Taylor","username":"david","avatar_template":"/user_avatar/meta.discourse.org/david/{size}/157490_2.png","created_at":"2017-05-16T10:28:58.496Z","like_count":1,"blurb":"I'm trying to use the docker image for tests both on my mac, and also https://meta.discourse.org/t/setting-up-plugin-continuous-integration-tests-on-travis-ci/59612 on travis . For a while now the qun...","post_number":1,"topic_title_headline":"QUnit tests won’t pass in discourse_dev docker image","topic_id":62797},{"id":187533,"name":"Sckott","username":"sckott","avatar_template":"/user_avatar/meta.discourse.org/sckott/{size}/115359_2.png","created_at":"2016-04-21T19:15:00.191Z","like_count":0,"blurb":"What's the best or fastest way to get Discourse installed on Travis for testing a client for the Discourse API ? It appears as though the discourse_api gem uses webmock so I think does not use a real ...","post_number":1,"topic_title_headline":"Testing a Discourse API client on Travis-CI","topic_id":42947},{"id":966756,"name":"Connor Parrish","username":"Connor_Parrish","avatar_template":"/user_avatar/meta.discourse.org/connor_parrish/{size}/225463_2.png","created_at":"2021-07-22T16:57:05.885Z","like_count":1,"blurb":"When you're conditionally adding a PostMenuButton using the plugin-api , the extra button is included in _extraButtons in between acceptance tests. When I run tests, if the tests where the button shou...","post_number":1,"topic_title_headline":"PostMenu’s ‘_extraButtons’ isn’t reset in between acceptance tests","topic_id":197887}],"topics":[{"id":49167,"title":"Write acceptance tests and component tests for Ember code in Discourse","fancy_title":"Write acceptance tests and component tests for Ember code in Discourse","slug":"write-acceptance-tests-and-component-tests-for-ember-code-in-discourse","posts_count":3,"reply_count":1,"highest_post_number":3,"created_at":"2016-08-24T20:48:02.492Z","last_posted_at":"2017-02-01T18:22:01.859Z","bumped":true,"bumped_at":"2017-02-01T18:22:01.859Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["tutorial","ember","testing"],"tags_descriptions":{},"category_id":56,"has_accepted_answer":false},{"id":32619,"title":"Developing Discourse Plugins - Part 6 - Add acceptance tests","fancy_title":"Developing Discourse Plugins - Part 6 - Add acceptance tests","slug":"developing-discourse-plugins-part-6-add-acceptance-tests","posts_count":33,"reply_count":26,"highest_post_number":38,"created_at":"2015-08-27T21:32:26.323Z","last_posted_at":"2022-06-02T11:06:38.274Z","bumped":true,"bumped_at":"2022-06-02T11:06:38.274Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["plugins","tutorial","plugin-guides","testing"],"tags_descriptions":{"plugins":""},"category_id":56,"has_accepted_answer":false},{"id":281579,"title":"End-to-end system testing for themes and theme components","fancy_title":"End-to-end system testing for themes and theme components","slug":"end-to-end-system-testing-for-themes-and-theme-components","posts_count":1,"reply_count":0,"highest_post_number":1,"created_at":"2023-10-24T23:13:37.118Z","last_posted_at":"2023-10-24T23:13:37.118Z","bumped":true,"bumped_at":"2023-11-13T23:20:01.596Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["how-to","themes"],"tags_descriptions":{"how-to":"How to guides contain steps to follow to solve a specific problem"},"category_id":56,"has_accepted_answer":false},{"id":66857,"title":"How to run Discourse core, plugin and theme QUnit test suites","fancy_title":"How to run Discourse core, plugin and theme QUnit test suites","slug":"how-to-run-discourse-core-plugin-and-theme-qunit-test-suites","posts_count":1,"reply_count":2,"highest_post_number":1,"created_at":"2017-07-26T14:09:58.032Z","last_posted_at":"2017-07-26T14:09:58.126Z","bumped":true,"bumped_at":"2023-09-04T17:56:33.079Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["how-to"],"tags_descriptions":{"how-to":"How to guides contain steps to follow to solve a specific problem"},"category_id":56,"has_accepted_answer":false},{"id":17155,"title":"Test Discourse in mobile screen emulator","fancy_title":"Test Discourse in mobile screen emulator","slug":"test-discourse-in-mobile-screen-emulator","posts_count":3,"reply_count":1,"highest_post_number":3,"created_at":"2014-07-03T11:45:09.360Z","last_posted_at":"2014-10-12T22:19:24.137Z","bumped":true,"bumped_at":"2014-10-12T22:19:24.137Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["how-to"],"tags_descriptions":{"how-to":"How to guides contain steps to follow to solve a specific problem"},"category_id":56,"has_accepted_answer":false},{"id":288363,"title":"Tip: when testing inbound email with fake user accounts...","fancy_title":"Tip: when testing inbound email with fake user accounts…","slug":"tip-when-testing-inbound-email-with-fake-user-accounts","posts_count":1,"reply_count":0,"highest_post_number":1,"created_at":"2023-12-12T10:51:07.868Z","last_posted_at":"2023-12-12T10:51:08.401Z","bumped":true,"bumped_at":"2023-12-12T10:51:08.401Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["email"],"tags_descriptions":{},"category_id":55,"has_accepted_answer":false},{"id":145744,"title":"Generate User API Keys for testing","fancy_title":"Generate User API Keys for testing","slug":"generate-user-api-keys-for-testing","posts_count":4,"reply_count":1,"highest_post_number":4,"created_at":"2020-03-26T21:31:38.269Z","last_posted_at":"2022-08-07T15:27:18.788Z","bumped":true,"bumped_at":"2022-08-07T15:27:18.788Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["how-to"],"tags_descriptions":{"how-to":"How to guides contain steps to follow to solve a specific problem"},"category_id":56,"has_accepted_answer":false},{"id":58298,"title":"Build a sandbox to test changes before making them live","fancy_title":"Build a sandbox to test changes before making them live","slug":"build-a-sandbox-to-test-changes-before-making-them-live","posts_count":21,"reply_count":13,"highest_post_number":21,"created_at":"2017-03-03T13:34:07.921Z","last_posted_at":"2022-02-16T23:18:54.680Z","bumped":true,"bumped_at":"2022-02-16T23:18:54.680Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["how-to"],"tags_descriptions":{"how-to":"How to guides contain steps to follow to solve a specific problem"},"category_id":55,"has_accepted_answer":false},{"id":118296,"title":"Migrate from tests-passed to stable","fancy_title":"Migrate from tests-passed to stable","slug":"migrate-from-tests-passed-to-stable","posts_count":12,"reply_count":7,"highest_post_number":12,"created_at":"2019-05-21T10:42:51.017Z","last_posted_at":"2019-06-21T15:55:53.072Z","bumped":true,"bumped_at":"2019-05-22T15:55:47.651Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":288705,"title":"502 Bad Gateway after online rebuild of tests-passed Production just now","fancy_title":"502 Bad Gateway after online rebuild of tests-passed Production just now","slug":"502-bad-gateway-after-online-rebuild-of-tests-passed-production-just-now","posts_count":6,"reply_count":1,"highest_post_number":6,"created_at":"2023-12-14T17:11:26.387Z","last_posted_at":"2024-01-13T17:37:27.535Z","bumped":true,"bumped_at":"2023-12-14T17:36:57.066Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":247546,"title":"Trouble on adding a simple unit test for Youtube oneboxing","fancy_title":"Trouble on adding a simple unit test for Youtube oneboxing","slug":"trouble-on-adding-a-simple-unit-test-for-youtube-oneboxing","posts_count":10,"reply_count":5,"highest_post_number":10,"created_at":"2022-12-02T20:49:55.713Z","last_posted_at":"2023-01-05T17:47:40.629Z","bumped":true,"bumped_at":"2022-12-06T17:47:28.771Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":["onebox","testing"],"tags_descriptions":{},"category_id":7,"has_accepted_answer":true},{"id":274165,"title":"Strange QUnit behaviour?: test failing because setting value doesn't survive","fancy_title":"Strange QUnit behaviour?: test failing because setting value doesn’t survive","slug":"strange-qunit-behaviour-test-failing-because-setting-value-doesnt-survive","posts_count":5,"reply_count":2,"highest_post_number":5,"created_at":"2023-08-07T10:05:22.298Z","last_posted_at":"2023-09-06T11:19:24.509Z","bumped":true,"bumped_at":"2023-08-07T11:24:27.150Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":true},{"id":249167,"title":"Is it possible to override the Site object with own fixture during Front End tests of a Plugin?","fancy_title":"Is it possible to override the Site object with own fixture during Front End tests of a Plugin?","slug":"is-it-possible-to-override-the-site-object-with-own-fixture-during-front-end-tests-of-a-plugin","posts_count":4,"reply_count":0,"highest_post_number":4,"created_at":"2022-12-16T18:32:29.079Z","last_posted_at":"2022-12-28T21:57:56.070Z","bumped":true,"bumped_at":"2022-12-28T21:57:56.070Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":true},{"id":286355,"title":"Acceptance tests failing on Github Actions","fancy_title":"Acceptance tests failing on Github Actions","slug":"acceptance-tests-failing-on-github-actions","posts_count":6,"reply_count":0,"highest_post_number":6,"created_at":"2023-11-22T18:46:46.128Z","last_posted_at":"2023-11-24T13:59:40.100Z","bumped":true,"bumped_at":"2023-11-24T13:59:40.100Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":true},{"id":91312,"title":"There was a problem sending the test email","fancy_title":"There was a problem sending the test email","slug":"there-was-a-problem-sending-the-test-email","posts_count":8,"reply_count":5,"highest_post_number":8,"created_at":"2018-07-01T17:12:48.366Z","last_posted_at":"2018-08-01T09:28:21.935Z","bumped":true,"bumped_at":"2018-07-02T09:28:16.014Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":287022,"title":"Strange migration error in tests during GH workflow","fancy_title":"Strange migration error in tests during GH workflow","slug":"strange-migration-error-in-tests-during-gh-workflow","posts_count":6,"reply_count":3,"highest_post_number":6,"created_at":"2023-11-29T23:18:11.686Z","last_posted_at":"2023-12-31T15:09:05.633Z","bumped":true,"bumped_at":"2023-12-01T15:08:31.257Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":true},{"id":238372,"title":"Smtp doctor test using port 465 even though its configured to use 2525","fancy_title":"Smtp doctor test using port 465 even though its configured to use 2525","slug":"smtp-doctor-test-using-port-465-even-though-its-configured-to-use-2525","posts_count":12,"reply_count":7,"highest_post_number":12,"created_at":"2022-09-07T20:22:10.976Z","last_posted_at":"2022-10-09T00:21:55.656Z","bumped":true,"bumped_at":"2022-09-09T00:21:48.272Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":146326,"title":"Sidekiq not running. Sidekiq heartbeat test failed, restarting","fancy_title":"Sidekiq not running. Sidekiq heartbeat test failed, restarting","slug":"sidekiq-not-running-sidekiq-heartbeat-test-failed-restarting","posts_count":16,"reply_count":9,"highest_post_number":16,"created_at":"2020-03-31T18:51:44.061Z","last_posted_at":"2020-06-10T01:39:28.366Z","bumped":true,"bumped_at":"2020-05-11T01:39:26.054Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":["unsupported-install"],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":255406,"title":"Tests for plugin that requires a plugin","fancy_title":"Tests for plugin that requires a plugin","slug":"tests-for-plugin-that-requires-a-plugin","posts_count":3,"reply_count":0,"highest_post_number":3,"created_at":"2023-02-16T22:12:10.344Z","last_posted_at":"2023-02-28T16:10:36.498Z","bumped":true,"bumped_at":"2023-02-28T20:21:06.122Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":true},{"id":246069,"title":"Need to restart Ember in order to test front-end changes","fancy_title":"Need to restart Ember in order to test front-end changes","slug":"need-to-restart-ember-in-order-to-test-front-end-changes","posts_count":5,"reply_count":1,"highest_post_number":5,"created_at":"2022-11-18T17:03:45.900Z","last_posted_at":"2022-11-18T18:17:53.648Z","bumped":true,"bumped_at":"2022-11-18T18:17:53.648Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":true},{"id":227090,"title":"Bitnami Discourse VM on Virtualbox + SMTP mail-server for testing","fancy_title":"Bitnami Discourse VM on Virtualbox + SMTP mail-server for testing","slug":"bitnami-discourse-vm-on-virtualbox-smtp-mail-server-for-testing","posts_count":3,"reply_count":0,"highest_post_number":3,"created_at":"2022-05-15T18:02:33.136Z","last_posted_at":"2022-05-16T09:10:43.794Z","bumped":true,"bumped_at":"2022-05-16T09:10:43.794Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":["unsupported-install"],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":145781,"title":"Test emails sent but","fancy_title":"Test emails sent but","slug":"test-emails-sent-but","posts_count":5,"reply_count":2,"highest_post_number":5,"created_at":"2020-03-27T07:14:10.116Z","last_posted_at":"2020-04-29T02:52:31.927Z","bumped":true,"bumped_at":"2020-03-30T02:52:29.062Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":137496,"title":"Sidekiq heartbeat test failed, restarting","fancy_title":"Sidekiq heartbeat test failed, restarting","slug":"sidekiq-heartbeat-test-failed-restarting","posts_count":13,"reply_count":8,"highest_post_number":13,"created_at":"2020-01-01T11:20:33.492Z","last_posted_at":"2020-02-11T23:09:42.375Z","bumped":true,"bumped_at":"2020-01-12T23:09:39.730Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":212684,"title":"MailGun & Discourse: Sandbox subdomains are for test purposes only. Please add your own domain","fancy_title":"MailGun & Discourse: Sandbox subdomains are for test purposes only. Please add your own domain","slug":"mailgun-discourse-sandbox-subdomains-are-for-test-purposes-only-please-add-your-own-domain","posts_count":4,"reply_count":2,"highest_post_number":5,"created_at":"2021-12-20T13:52:10.405Z","last_posted_at":"2022-01-19T15:27:11.368Z","bumped":true,"bumped_at":"2021-12-20T15:26:24.911Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":102035,"title":"Test admin features without having to install Discourse","fancy_title":"Test admin features without having to install Discourse","slug":"test-admin-features-without-having-to-install-discourse","posts_count":6,"reply_count":2,"highest_post_number":6,"created_at":"2018-11-14T15:20:59.484Z","last_posted_at":"2021-09-09T07:35:18.270Z","bumped":true,"bumped_at":"2021-09-09T07:35:18.270Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":121299,"title":"Install discourse with a staging (test) ssl certificate","fancy_title":"Install discourse with a staging (test) ssl certificate","slug":"install-discourse-with-a-staging-test-ssl-certificate","posts_count":5,"reply_count":1,"highest_post_number":5,"created_at":"2019-06-25T17:12:34.552Z","last_posted_at":"2023-04-01T03:25:32.925Z","bumped":true,"bumped_at":"2019-09-04T15:15:31.153Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":118568,"title":"Problem testing Badge Query from Data Explorer","fancy_title":"Problem testing Badge Query from Data Explorer","slug":"problem-testing-badge-query-from-data-explorer","posts_count":5,"reply_count":1,"highest_post_number":6,"created_at":"2019-05-24T00:14:35.814Z","last_posted_at":"2019-05-24T00:46:42.560Z","bumped":true,"bumped_at":"2019-05-24T00:46:42.560Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":["sql-triggered-badge"],"tags_descriptions":{"sql-triggered-badge":"SQL queries for custom triggered badges"},"category_id":148,"has_accepted_answer":true},{"id":115912,"title":"New iOS mobile app beta available for testing","fancy_title":"New iOS mobile app beta available for testing","slug":"new-ios-mobile-app-beta-available-for-testing","posts_count":49,"reply_count":31,"highest_post_number":49,"created_at":"2019-04-25T01:24:56.608Z","last_posted_at":"2019-05-31T17:31:02.516Z","bumped":true,"bumped_at":"2020-01-21T17:07:37.817Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":224560,"title":"502 Bad Gateway after trying to rebuild test-passed branch","fancy_title":"502 Bad Gateway after trying to rebuild test-passed branch","slug":"502-bad-gateway-after-trying-to-rebuild-test-passed-branch","posts_count":6,"reply_count":1,"highest_post_number":6,"created_at":"2022-04-17T21:46:04.598Z","last_posted_at":"2022-04-17T22:15:37.255Z","bumped":true,"bumped_at":"2022-04-17T22:15:37.255Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":false},{"id":173019,"title":"Forum offline due to failed rebuilds on Tests-Pass","fancy_title":"Forum offline due to failed rebuilds on Tests-Pass","slug":"forum-offline-due-to-failed-rebuilds-on-tests-pass","posts_count":2,"reply_count":0,"highest_post_number":2,"created_at":"2020-12-11T18:44:17.952Z","last_posted_at":"2020-12-11T19:12:11.141Z","bumped":true,"bumped_at":"2020-12-11T19:12:11.141Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":false},{"id":196501,"title":"[A/B Testing] Changing parent CSS class based on experiment variable","fancy_title":"[A/B Testing] Changing parent CSS class based on experiment variable","slug":"a-b-testing-changing-parent-css-class-based-on-experiment-variable","posts_count":3,"reply_count":1,"highest_post_number":3,"created_at":"2021-07-08T19:58:55.503Z","last_posted_at":"2021-07-15T18:19:21.092Z","bumped":true,"bumped_at":"2021-07-15T18:19:21.092Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":282856,"title":"Code-level performance testing","fancy_title":"Code-level performance testing","slug":"code-level-performance-testing","posts_count":2,"reply_count":0,"highest_post_number":2,"created_at":"2023-10-20T03:44:49.568Z","last_posted_at":"2023-10-23T23:29:59.741Z","bumped":true,"bumped_at":"2023-10-23T23:29:59.741Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":272112,"title":"Running core tests in docker environment","fancy_title":"Running core tests in docker environment","slug":"running-core-tests-in-docker-environment","posts_count":1,"reply_count":0,"highest_post_number":1,"created_at":"2023-07-19T12:45:57.266Z","last_posted_at":"2023-07-19T12:45:57.446Z","bumped":true,"bumped_at":"2023-07-19T12:45:57.446Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["docker","spec","testing"],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":87295,"title":"Email test from the console?","fancy_title":"Email test from the console?","slug":"email-test-from-the-console","posts_count":4,"reply_count":1,"highest_post_number":4,"created_at":"2018-05-11T22:03:23.101Z","last_posted_at":"2018-05-12T00:55:41.843Z","bumped":true,"bumped_at":"2018-05-12T00:55:41.843Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":59577,"title":"Plugin QUnit tests are not running as part of rake qunit:test","fancy_title":"Plugin QUnit tests are not running as part of rake qunit:test","slug":"plugin-qunit-tests-are-not-running-as-part-of-rake-qunit-test","posts_count":8,"reply_count":5,"highest_post_number":8,"created_at":"2017-03-20T20:29:06.536Z","last_posted_at":"2017-07-17T18:26:45.188Z","bumped":true,"bumped_at":"2017-07-17T18:26:45.188Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":181856,"title":"How does the Discourse team do usability testing?","fancy_title":"How does the Discourse team do usability testing?","slug":"how-does-the-discourse-team-do-usability-testing","posts_count":5,"reply_count":2,"highest_post_number":6,"created_at":"2021-03-03T18:09:29.357Z","last_posted_at":"2021-03-04T15:23:46.571Z","bumped":true,"bumped_at":"2021-03-04T15:23:46.571Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":3,"has_accepted_answer":false},{"id":108110,"title":"Advice on writing Ruby tests for plugins","fancy_title":"Advice on writing Ruby tests for plugins","slug":"advice-on-writing-ruby-tests-for-plugins","posts_count":15,"reply_count":13,"highest_post_number":15,"created_at":"2019-02-01T09:52:27.051Z","last_posted_at":"2019-05-03T03:54:47.163Z","bumped":true,"bumped_at":"2019-05-03T03:54:47.163Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":57292,"title":"Acceptance test content is present on page","fancy_title":"Acceptance test content is present on page","slug":"acceptance-test-content-is-present-on-page","posts_count":7,"reply_count":3,"highest_post_number":7,"created_at":"2017-02-13T02:48:10.108Z","last_posted_at":"2017-02-14T05:55:31.520Z","bumped":true,"bumped_at":"2017-02-14T05:55:31.520Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":291039,"title":"Google Search Console/Schema Markup test tool \"Google Rich Results Test\": mobile-view instead of crawler-view","fancy_title":"Google Search Console/Schema Markup test tool “Google Rich Results Test”: mobile-view instead of crawler-view","slug":"google-search-console-schema-markup-test-tool-google-rich-results-test-mobile-view-instead-of-crawler-view","posts_count":3,"reply_count":1,"highest_post_number":3,"created_at":"2024-01-09T16:53:57.797Z","last_posted_at":"2024-01-09T17:17:09.039Z","bumped":true,"bumped_at":"2024-01-09T17:17:09.039Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":1,"has_accepted_answer":false},{"id":267737,"title":"Experiments with Model-Based Testing","fancy_title":"Experiments with Model-Based Testing","slug":"experiments-with-model-based-testing","posts_count":1,"reply_count":0,"highest_post_number":1,"created_at":"2023-06-08T22:35:51.784Z","last_posted_at":"2023-06-08T22:35:51.914Z","bumped":true,"bumped_at":"2023-06-08T22:35:51.914Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":233389,"title":"Qunit tests not deterministic in Plugin?","fancy_title":"Qunit tests not deterministic in Plugin?","slug":"qunit-tests-not-deterministic-in-plugin","posts_count":2,"reply_count":0,"highest_post_number":2,"created_at":"2022-07-20T08:48:28.757Z","last_posted_at":"2022-07-20T11:03:42.522Z","bumped":true,"bumped_at":"2022-07-20T11:03:42.522Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":135876,"title":"Issues migrating test database","fancy_title":"Issues migrating test database","slug":"issues-migrating-test-database","posts_count":9,"reply_count":1,"highest_post_number":9,"created_at":"2019-12-12T21:01:31.303Z","last_posted_at":"2021-03-02T16:44:20.120Z","bumped":true,"bumped_at":"2021-03-02T16:44:20.120Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":175413,"title":"Acceptance Test for Markdown Extension?","fancy_title":"Acceptance Test for Markdown Extension?","slug":"acceptance-test-for-markdown-extension","posts_count":3,"reply_count":0,"highest_post_number":4,"created_at":"2021-01-07T19:56:04.931Z","last_posted_at":"2021-01-10T11:26:16.888Z","bumped":true,"bumped_at":"2021-01-10T16:30:56.164Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":150786,"title":"Auto migrate test database schema","fancy_title":"Auto migrate test database schema","slug":"auto-migrate-test-database-schema","posts_count":2,"reply_count":0,"highest_post_number":2,"created_at":"2020-05-08T04:27:56.739Z","last_posted_at":"2020-05-08T13:15:45.247Z","bumped":true,"bumped_at":"2020-05-08T13:15:45.247Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":118639,"title":"Building Tests for New discourse_api Endpoints","fancy_title":"Building Tests for New discourse_api Endpoints","slug":"building-tests-for-new-discourse-api-endpoints","posts_count":20,"reply_count":9,"highest_post_number":21,"created_at":"2019-05-24T22:21:24.896Z","last_posted_at":"2019-10-02T21:13:35.140Z","bumped":true,"bumped_at":"2019-10-02T21:36:26.639Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":126025,"title":"Seed or API calls to create test users","fancy_title":"Seed or API calls to create test users","slug":"seed-or-api-calls-to-create-test-users","posts_count":7,"reply_count":5,"highest_post_number":7,"created_at":"2019-08-17T02:13:06.578Z","last_posted_at":"2019-08-19T11:50:36.137Z","bumped":true,"bumped_at":"2019-08-19T11:50:36.137Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":71118,"title":"Smoke-testing plugins during upgrade process","fancy_title":"Smoke-testing plugins during upgrade process","slug":"smoke-testing-plugins-during-upgrade-process","posts_count":22,"reply_count":17,"highest_post_number":22,"created_at":"2017-10-01T08:18:48.067Z","last_posted_at":"2017-10-02T23:44:59.852Z","bumped":true,"bumped_at":"2017-10-02T23:44:59.852Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["pr-welcome"],"tags_descriptions":{"pr-welcome":"You're welcome to submit a Github pull request that implements this"},"category_id":2,"has_accepted_answer":false},{"id":62797,"title":"QUnit tests won't pass in discourse_dev docker image","fancy_title":"QUnit tests won’t pass in discourse_dev docker image","slug":"qunit-tests-wont-pass-in-discourse-dev-docker-image","posts_count":20,"reply_count":11,"highest_post_number":21,"created_at":"2017-05-16T10:28:58.397Z","last_posted_at":"2017-07-25T15:50:27.716Z","bumped":true,"bumped_at":"2017-07-25T15:50:27.716Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":42947,"title":"Testing a Discourse API client on Travis-CI","fancy_title":"Testing a Discourse API client on Travis-CI","slug":"testing-a-discourse-api-client-on-travis-ci","posts_count":5,"reply_count":3,"highest_post_number":5,"created_at":"2016-04-21T19:15:00.130Z","last_posted_at":"2016-04-22T03:03:47.976Z","bumped":true,"bumped_at":"2016-04-22T03:03:47.976Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":197887,"title":"PostMenu's '_extraButtons' isn't reset in between acceptance tests","fancy_title":"PostMenu’s ‘_extraButtons’ isn’t reset in between acceptance tests","slug":"postmenus-extrabuttons-isnt-reset-in-between-acceptance-tests","posts_count":2,"reply_count":0,"highest_post_number":4,"created_at":"2021-07-22T16:57:05.803Z","last_posted_at":"2021-08-04T09:11:29.538Z","bumped":true,"bumped_at":"2021-08-04T09:11:29.538Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false}],"users":[],"categories":[],"tags":[],"groups":[],"grouped_search_result":{"more_posts":null,"more_users":null,"more_categories":null,"term":"testing","search_log_id":2089836,"more_full_page_results":true,"can_create_topic":false,"error":null,"post_ids":[218354,138484,1381521,311252,59431,1419473,722424,266441,582008,1421414,1204506,1339808,1211823,1408389,443129,1412219,1160803,725228,1240758,1197679,1103913,722608,679950,1033333,498559,596557,583541,569209,1090520,860593,960683,1389188,1329017,421687,272436,899205,530438,260691,1432567,1305996,1135474,672801,871105,747613,583961,621707,334186,288991,187533,966756],"user_ids":[],"category_ids":[],"tag_ids":[],"group_ids":[]}} \ No newline at end of file diff --git a/spec/fixtures/search_meta/search_with_categories.json b/spec/fixtures/search_meta/search_with_categories.json index 6f412e0fe..fbb8e966e 100644 --- a/spec/fixtures/search_meta/search_with_categories.json +++ b/spec/fixtures/search_meta/search_with_categories.json @@ -1 +1 @@ -{"posts":[{"id":218354,"name":"Robin Ward","username":"eviltrout","avatar_template":"/user_avatar/meta.discourse.org/eviltrout/{size}/5275_2.png","created_at":"2016-08-24T20:48:02.587Z","like_count":15,"blurb":"Automated tests are a great way to protect your code against future regressions. Many people are familiar with how to do this in our Rails codebase with http://rspec.info/ rspec , but the Javascript s...","post_number":1,"topic_title_headline":"Write acceptance tests and component tests for Ember code in Discourse","topic_id":49167},{"id":138484,"name":"Robin Ward","username":"eviltrout","avatar_template":"/user_avatar/meta.discourse.org/eviltrout/{size}/5275_2.png","created_at":"2015-08-27T21:32:26.407Z","like_count":29,"blurb":"Previous tutorial: https://meta.discourse.org/t/developing-discourse-plugins-part-5-add-an-admin-interface/31761 Developing Discourse Plugins - Part 5 - Add an admin interface Did you know that Discou...","post_number":1,"topic_title_headline":"Developing Discourse Plugins - Part 6 - Add acceptance tests","topic_id":32619},{"id":1381521,"name":"Alan Tan","username":"tgxworld","avatar_template":"/user_avatar/meta.discourse.org/tgxworld/{size}/106117_2.png","created_at":"2023-10-24T23:13:37.118Z","like_count":16,"blurb":"Writing automated tests for themes is an important part of the theme development process which can help ensure that the features being introduced by a theme continues to work well overtime with core D...","post_number":1,"topic_title_headline":"End-to-end system testing for themes and theme components","topic_id":281579},{"id":311252,"name":"David Taylor","username":"david","avatar_template":"/user_avatar/meta.discourse.org/david/{size}/157490_2.png","created_at":"2017-07-26T14:09:58.126Z","like_count":17,"blurb":"Discourse has extensive frontend tests for core, plugins and themes. Once you have a functioning local development environment, those tests can be run locally in a number of different ways. Running te...","post_number":1,"topic_title_headline":"How to run Discourse core, plugin and theme QUnit test suites","topic_id":66857},{"id":59431,"name":"Erlend Sogge Heggen","username":"erlend_sh","avatar_template":"/user_avatar/meta.discourse.org/erlend_sh/{size}/119475_2.png","created_at":"2014-07-03T11:45:09.463Z","like_count":5,"blurb":"...view=1 to an URL, but the emulator has the added benefit of letting you select the screen profile of a specific device. I also tested all of the most popular online screen emulators, but unfortunately...","post_number":1,"topic_title_headline":"Test Discourse in mobile screen emulator","topic_id":17155},{"id":1419473,"name":"","username":"ToddZ","avatar_template":"/user_avatar/meta.discourse.org/toddz/{size}/328350_2.png","created_at":"2023-12-12T10:51:08.401Z","like_count":2,"blurb":"...amount of time troubleshooting inbound email because Discourse was rejecting every reply-by-email from my fake users. It had worked fine when I first tested several weeks ago… I finally realized that ...","post_number":1,"topic_title_headline":"Tip: when testing inbound email with fake user accounts…","topic_id":288363},{"id":722424,"name":"Falco","username":"Falco","avatar_template":"/user_avatar/meta.discourse.org/falco/{size}/179432_2.png","created_at":"2020-03-26T21:31:38.463Z","like_count":17,"blurb":"Continuing the discussion from https://meta.discourse.org/t/user-api-keys-specification/48536 User API keys specification : I created a small utility script in order to test User API keys locally. Fir...","post_number":1,"topic_title_headline":"Generate User API Keys for testing","topic_id":145744},{"id":266441,"name":"Andrew Waugh","username":"JagWaugh","avatar_template":"/user_avatar/meta.discourse.org/jagwaugh/{size}/69335_2.png","created_at":"2017-03-03T13:34:08.009Z","like_count":19,"blurb":"Regardless of if you're a moderator or an admin, you will no doubt at some time think about making some change to your live site and wonder if this will bring shame on you, and/or cause yourself an en...","post_number":1,"topic_title_headline":"Build a sandbox to test changes before making them live","topic_id":58298},{"id":582008,"name":"","username":"Wurzelseppi","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/w/eada6e/{size}.png","created_at":"2019-05-21T10:42:51.099Z","like_count":0,"blurb":"Hi guys, just wanted to migrate from 2.3.0beta9 to stable release and got this error: What can I do here ? Caused by: PG::UndefinedColumn: ERROR: column \"email_private_messages\" of relation \"user_opti...","post_number":1,"topic_title_headline":"Migrate from tests-passed to stable","topic_id":118296},{"id":1421414,"name":"Robert","username":"merefield","avatar_template":"/user_avatar/meta.discourse.org/merefield/{size}/176214_2.png","created_at":"2023-12-14T17:11:26.575Z","like_count":1,"blurb":"Restarting the server, restarting the container, console rebuilding doesn't help. Container is up as I can ./launcher enter app Got a bunch of these in logs, ideas on how to investigate redis failure?...","post_number":1,"topic_title_headline":"502 Bad Gateway after online rebuild of tests-passed Production just now","topic_id":288705},{"id":1204506,"name":"Coin-coin le Canapin","username":"Canapin","avatar_template":"/user_avatar/meta.discourse.org/canapin/{size}/119591_2.png","created_at":"2022-12-02T20:49:55.867Z","like_count":1,"blurb":"Hi! I want to add support of /shorts/ Youtube link. My modification of the YoutubeOnebox class works, but it is required that I add a test in https://github.com/discourse/discourse/blob/493d437e79f88a...","post_number":1,"topic_title_headline":"Trouble on adding a simple unit test for Youtube oneboxing","topic_id":247546},{"id":1339808,"name":"Robert","username":"merefield","avatar_template":"/user_avatar/meta.discourse.org/merefield/{size}/176214_2.png","created_at":"2023-08-07T10:05:22.444Z","like_count":2,"blurb":"I have a strange issue with QUnit. This test is extremely simple and should be straightforward … but … A plugin setting is changing from those I have set up. https://github.com/paviliondev/discourse-l...","post_number":1,"topic_title_headline":"Strange QUnit behaviour?: test failing because setting value doesn’t survive","topic_id":274165},{"id":1211823,"name":"Robert","username":"merefield","avatar_template":"/user_avatar/meta.discourse.org/merefield/{size}/176214_2.png","created_at":"2022-12-16T18:32:29.231Z","like_count":1,"blurb":"...presenting formatted location on User Card by merefield · Pull Request #73 · paviliondev/discourse-locations · GitHub I'm attempting to cover the change with a new Front End test. But the test fails t...","post_number":1,"topic_title_headline":"Is it possible to override the Site object with own fixture during Front End tests of a Plugin?","topic_id":249167},{"id":1408389,"name":"Pierre Romera","username":"pirhoo","avatar_template":"/user_avatar/meta.discourse.org/pirhoo/{size}/120058_2.png","created_at":"2023-11-22T18:46:46.469Z","like_count":5,"blurb":"...m setting up a new plugin based on the https://github.com/discourse/discourse-plugin-skeleton/tree/main/assets skeleton you provided which already helped me a lot. I am now writing tests, both for the...","post_number":1,"topic_title_headline":"Acceptance tests failing on Github Actions","topic_id":286355},{"id":443129,"name":"JK Baseer","username":"JKBaseer","avatar_template":"/user_avatar/meta.discourse.org/jkbaseer/{size}/80471_2.png","created_at":"2018-07-01T17:12:48.446Z","like_count":0,"blurb":"...but still could myself. Background: I installed discourse using digitalocean oneclick installer. The website is running under http://forum.example.org forum.example.org without any problem except the ...","post_number":1,"topic_title_headline":"There was a problem sending the test email","topic_id":91312},{"id":1412219,"name":"Robert","username":"merefield","avatar_template":"/user_avatar/meta.discourse.org/merefield/{size}/176214_2.png","created_at":"2023-11-29T23:18:11.815Z","like_count":1,"blurb":"I'm trying to create a foreign key relationship with the Topics table. The problem is it is failing in github workflow test environment during tests for the strangest reason, it is trying to access a ...","post_number":1,"topic_title_headline":"Strange migration error in tests during GH workflow","topic_id":287022},{"id":1160803,"name":"Bryan Joseph","username":"Bryan_Joseph","avatar_template":"/user_avatar/meta.discourse.org/bryan_joseph/{size}/273248_2.png","created_at":"2022-09-07T20:22:11.191Z","like_count":1,"blurb":"...SETTINGS ==================== DISCOURSE_HOSTNAME=url SMTP_ADDRESS=smtp.mailgun.org DEVELOPER_EMAILS=REDACTED SMTP_PASSWORD=REDACTED SMTP_PORT=2525 SMTP_USER_NAME=url LETSENCRYPT_ACCOUNT_EMAIL=REDACTED...","post_number":1,"topic_title_headline":"Smtp doctor test using port 465 even though its configured to use 2525","topic_id":238372},{"id":725228,"name":"james.network","username":"sunjam","avatar_template":"/user_avatar/meta.discourse.org/sunjam/{size}/175682_2.png","created_at":"2020-03-31T18:51:44.216Z","like_count":0,"blurb":"...Redis or updating it; it hasn't really been touched in the last 8+ months. I have not personally dealt with Redis before, but our Tests-Pass Discourse instance was setup using https://hub.docker.com/r...","post_number":1,"topic_title_headline":"Sidekiq not running. Sidekiq heartbeat test failed, restarting","topic_id":146326},{"id":1240758,"name":"Jay Pfaffman","username":"pfaffman","avatar_template":"/user_avatar/meta.discourse.org/pfaffman/{size}/120154_2.png","created_at":"2023-02-16T22:12:10.456Z","like_count":4,"blurb":"I see that the discourse-plugin-skeleton now has this: uses: discourse/.github/.github/workflows/discourse-plugin.yml@v1 so we don't have to keep updating stuff. But I have a plugin that requires the ...","post_number":1,"topic_title_headline":"Tests for plugin that requires a plugin","topic_id":255406},{"id":1197679,"name":"","username":"SilK","avatar_template":"/user_avatar/meta.discourse.org/silk/{size}/268124_2.png","created_at":"2022-11-18T17:03:46.056Z","like_count":0,"blurb":"...a new dev environment for working on plugins. Discourse is up to date with the main branch. I need to restart Ember in order to test changes made to the front end. This includes changes to Handlebars,...","post_number":1,"topic_title_headline":"Need to restart Ember in order to test front-end changes","topic_id":246069},{"id":1103913,"name":"Banibrata Dutta","username":"bdutta","avatar_template":"/user_avatar/meta.discourse.org/bdutta/{size}/259973_2.png","created_at":"2022-05-15T18:02:33.290Z","like_count":0,"blurb":"...only in a captive host-only testbed, so wondering if there is any local network SMTP daemon / service that I could start to complete the testing ? I'm happy with 100% command line mail client and serv...","post_number":1,"topic_title_headline":"Bitnami Discourse VM on Virtualbox + SMTP mail-server for testing","topic_id":227090},{"id":722608,"name":"Lona Lee","username":"Lona_Lee","avatar_template":"/user_avatar/meta.discourse.org/lona_lee/{size}/169072_2.png","created_at":"2020-03-27T07:14:10.239Z","like_count":1,"blurb":"Hello. I'm trying to get email setup working on my discourse instance. Done set-up properly and looks fine(no errors), so sent test emails. (logs confirmed : \"Admin\" - \"Emails\" - \"Sent\") However, I ha...","post_number":1,"topic_title_headline":"Test emails sent but","topic_id":145781},{"id":679950,"name":"Oleg Bovykin","username":"arrowcircle","avatar_template":"/user_avatar/meta.discourse.org/arrowcircle/{size}/100035_2.png","created_at":"2020-01-01T11:20:33.618Z","like_count":1,"blurb":"Hi! I found strange error in my admin page, that sidekiq is not running. I opened logs and found hundreds errors like: /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/logster-2.5.1/lib/logster/logger...","post_number":1,"topic_title_headline":"Sidekiq heartbeat test failed, restarting","topic_id":137496},{"id":1033333,"name":"М. М.","username":"М_М","avatar_template":"/user_avatar/meta.discourse.org/м_м/{size}/243710_2.png","created_at":"2021-12-20T13:52:10.488Z","like_count":0,"blurb":"...user, the logs say like this Job exception: could not get 3xx (421: 421 Domain sandbox410fe5c7bb85483c941c05b4ec5f3495.mailgun.org is not allowed to send: Sandbox subdomains are for test purposes only...","post_number":1,"topic_title_headline":"MailGun & Discourse: Sandbox subdomains are for test purposes only. Please add your own domain","topic_id":212684},{"id":498559,"name":"","username":"desrocchi","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/d/eb9ed0/{size}.png","created_at":"2018-11-14T15:20:59.607Z","like_count":0,"blurb":"Is there a way for me to see or test the admin options in the demo area? I am just a moderator on the platform we use but I would like to see which options could be of use without having to install th...","post_number":1,"topic_title_headline":"Test admin features without having to install Discourse","topic_id":102035},{"id":596557,"name":"Flaviu","username":"UnivacTwo","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/u/df705f/{size}.png","created_at":"2019-06-25T17:12:34.710Z","like_count":0,"blurb":"Let's encrypt has a limit of how many certificates can be generated in a week for the same domain. Unfortunately we reach this limit and we cannot generate a new certificate this week. We did a backup...","post_number":1,"topic_title_headline":"Install discourse with a staging (test) ssl certificate","topic_id":121299},{"id":583541,"name":"mark78","username":"Mark_Schmucker","avatar_template":"/user_avatar/meta.discourse.org/mark_schmucker/{size}/124810_2.png","created_at":"2019-05-24T00:14:35.895Z","like_count":0,"blurb":"Should I be able to run any Badge Query in https://meta.discourse.org/t/32566 Data Explorer ? I want to create a custom Badge Query using \"Appreciated\" as a starting point. I type the Appreciated quer...","post_number":1,"topic_title_headline":"Problem testing Badge Query from Data Explorer","topic_id":118568},{"id":569209,"name":"Penar Musaraj","username":"pmusaraj","avatar_template":"/user_avatar/meta.discourse.org/pmusaraj/{size}/119489_2.png","created_at":"2019-04-25T01:24:56.741Z","like_count":26,"blurb":"...device and installing the app via TestFlight: https://testflight.apple.com/join/NkdBQgmg testflight.apple.com https://testflight.apple.com/join/NkdBQgmg TestFlight - Apple Using TestFlight is a great ...","post_number":1,"topic_title_headline":"New iOS mobile app beta available for testing","topic_id":115912},{"id":1090520,"name":"Mac玩儿法","username":"waerfa","avatar_template":"/user_avatar/meta.discourse.org/waerfa/{size}/216044_2.png","created_at":"2022-04-17T21:46:04.755Z","like_count":0,"blurb":"...rebuild the container: git pull ./launcher rebuild app I got the fatal error which shows: FAILED -------------------- Pups::ExecError: cd /var/www/discourse & & git fetch --depth 1 origin tests-passed...","post_number":1,"topic_title_headline":"502 Bad Gateway after trying to rebuild test-passed branch","topic_id":224560},{"id":860593,"name":"james.network","username":"sunjam","avatar_template":"/user_avatar/meta.discourse.org/sunjam/{size}/175682_2.png","created_at":"2020-12-11T18:44:18.021Z","like_count":1,"blurb":"Continuing the discussion from https://meta.discourse.org/t/postgresql-13-update/172563/27 PostgreSQL 13 update : Run into trouble while updating 2.7.0beta1 Tests-Pass in order to remove some troubles...","post_number":1,"topic_title_headline":"Forum offline due to failed rebuilds on Tests-Pass","topic_id":173019},{"id":960683,"name":"","username":"daniyal","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/d/58f4c7/{size}.png","created_at":"2021-07-08T19:58:55.634Z","like_count":3,"blurb":"...which we want to experiment. An example would be to experiment different styles of topic list view. For this we are using Google Optimize A/B testing. Currently we plan to show theme without changes t...","post_number":1,"topic_title_headline":"[A/B Testing] Changing parent CSS class based on experiment variable","topic_id":196501},{"id":1389188,"name":"Angus McLeod","username":"angus","avatar_template":"/user_avatar/meta.discourse.org/angus/{size}/341715_2.png","created_at":"2023-10-20T03:44:49.737Z","like_count":7,"blurb":"I've been looking at the performance of the https://meta.discourse.org/t/activitypub-plugin/266794 ActivityPub plugin recently and considering the best ways to reliably test, and prove, performance fo...","post_number":1,"topic_title_headline":"Code-level performance testing","topic_id":282856},{"id":1329017,"name":"","username":"dodibi","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/d/9fc348/{size}.png","created_at":"2023-07-19T12:45:57.446Z","like_count":0,"blurb":"Hello everyone! I'm currently facing some challenges while configuring my local environment to run discourse tests in a docker container. My main objective is to run the core tests with plugins attach...","post_number":1,"topic_title_headline":"Running core tests in docker environment","topic_id":272112},{"id":421687,"name":"Jay Pfaffman","username":"pfaffman","avatar_template":"/user_avatar/meta.discourse.org/pfaffman/{size}/120154_2.png","created_at":"2018-05-11T22:03:23.232Z","like_count":0,"blurb":"Is there a way to initiate an email test from the Rails console? For a zillion reasons I would love to be able to send a test email without having to create an account. I've looked in config/routes.rb...","post_number":1,"topic_title_headline":"Email test from the console?","topic_id":87295},{"id":272436,"name":"David Taylor","username":"david","avatar_template":"/user_avatar/meta.discourse.org/david/{size}/157490_2.png","created_at":"2017-03-20T20:29:06.608Z","like_count":1,"blurb":"It is my understanding that running rake qunit:test should run all of the qunit tests in Discourse, including those for any installed plugins. However, when I run the task in the docker development en...","post_number":1,"topic_title_headline":"Plugin QUnit tests are not running as part of rake qunit:test","topic_id":59577},{"id":899205,"name":"","username":"JQ331","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/j/41988e/{size}.png","created_at":"2021-03-03T18:09:29.452Z","like_count":6,"blurb":"I came across this https://blog.codinghorror.com/low-fi-usability-testing/ excellent article on how to do low-fi usability testing by @codinghorror . Usability testing (and user testing in general) is...","post_number":1,"topic_title_headline":"How does the Discourse team do usability testing?","topic_id":181856},{"id":530438,"name":"","username":"kleinfreund","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/k/a6a055/{size}.png","created_at":"2019-02-01T09:52:27.150Z","like_count":1,"blurb":"One can write tests for the backend of a plugin. For example, I created the following file in my plugin directory: spec/lib/route_store_spec.rb : require 'rails_helper' describe MyPlugin::RouteStore d...","post_number":1,"topic_title_headline":"Advice on writing Ruby tests for plugins","topic_id":108110},{"id":260691,"name":"Rimian Perkins","username":"rimian","avatar_template":"/user_avatar/meta.discourse.org/rimian/{size}/120658_2.png","created_at":"2017-02-13T02:48:10.177Z","like_count":0,"blurb":"What's the best way to (QUnit) assert an element on the page has some content in it? This passes: ok($.trim($('.foo').text()) == 'bar', 'content bar renders on page'); But isn't very practical. Is the...","post_number":1,"topic_title_headline":"Acceptance test content is present on page","topic_id":57292},{"id":1432567,"name":"Ayke","username":"rrit","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/r/b5ac83/{size}.png","created_at":"2024-01-09T16:53:57.916Z","like_count":0,"blurb":"Right now Discourse on meta.discourse.org serves the mobile-view instead of the crawler-view to the https://search.google.com/test/rich-results Google Rich Results Test . As there is no Schema Markup ...","post_number":1,"topic_title_headline":"Google Search Console/Schema Markup test tool “Google Rich Results Test”: mobile-view instead of crawler-view","topic_id":291039},{"id":1305996,"name":"Larry Diehl","username":"larrytheliquid","avatar_template":"/user_avatar/meta.discourse.org/larrytheliquid/{size}/310576_2.png","created_at":"2023-06-08T22:35:51.914Z","like_count":1,"blurb":"Hi Discourse Community! :slight_smile: I've been working on tech ( https://colimit.io Colimit ) that helps people apply https://en.wikipedia.org/wiki/Model-based_testing Model-based testing to test th...","post_number":1,"topic_title_headline":"Experiments with Model-Based Testing","topic_id":267737},{"id":1135474,"name":"Robert","username":"merefield","avatar_template":"/user_avatar/meta.discourse.org/merefield/{size}/176214_2.png","created_at":"2022-07-20T08:48:28.868Z","like_count":0,"blurb":"...my case running: rake \"plugin:qunit[discourse-multilingual]\" with a branch installed. I'm declaring a function in my initializer (i'm extending I18n ) The tests sometimes (25%?) seem to run before the...","post_number":1,"topic_title_headline":"Qunit tests not deterministic in Plugin?","topic_id":233389},{"id":672801,"name":"Jay Pfaffman","username":"pfaffman","avatar_template":"/user_avatar/meta.discourse.org/pfaffman/{size}/120154_2.png","created_at":"2019-12-12T21:01:31.303Z","like_count":0,"blurb":"Thanks, @Mittineague ! After a while, that made sense. I even wrote a spec, but even before I added my spec (and when I reverted to before I added any code), specs fail because: An error occurred whil...","post_number":1,"topic_title_headline":"Issues migrating test database","topic_id":135876},{"id":871105,"name":"","username":"Alteras","avatar_template":"/user_avatar/meta.discourse.org/alteras/{size}/179824_2.png","created_at":"2021-01-07T19:56:05.071Z","like_count":4,"blurb":"Hello! I'm currently working on a Markdown Extension/plugin that adds quite a number of BBCode tags, and I am looking to write QUnit Acceptance tests for them (I got really tired of constantly checkin...","post_number":1,"topic_title_headline":"Acceptance Test for Markdown Extension?","topic_id":175413},{"id":747613,"name":"","username":"xrav3nz","avatar_template":"/user_avatar/meta.discourse.org/xrav3nz/{size}/76894_2.png","created_at":"2020-05-08T04:27:56.920Z","like_count":8,"blurb":"...development - #2 by taylorthurlow - A May Of WTFs - Ruby on Rails Discussions Not sure if we have explored this before, but Rails can automatically maintain test databse schema with ActiveRecord::Migr...","post_number":1,"topic_title_headline":"Auto migrate test database schema","topic_id":150786},{"id":583961,"name":"Kim Miller","username":"kimardenmiller","avatar_template":"/user_avatar/meta.discourse.org/kimardenmiller/{size}/119631_2.png","created_at":"2019-05-24T22:21:24.996Z","like_count":3,"blurb":"Adding some polls API endpoints for PR to discourse_api, which work fine. Now I'm trying to understand how to create tests before submitting the PR, e.g.: require 'spec_helper' describe DiscourseApi::...","post_number":1,"topic_title_headline":"Building Tests for New discourse_api Endpoints","topic_id":118639},{"id":621707,"name":"Andrew Lank","username":"alank","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/a/c89c15/{size}.png","created_at":"2019-08-17T02:13:06.679Z","like_count":1,"blurb":"In my development and testing I'm running a Discourse instance (Docker Discourse from Bitnami) and it fulfills most of my API testing for our API service which talks to Discourse, however I now need t...","post_number":1,"topic_title_headline":"Seed or API calls to create test users","topic_id":126025},{"id":334186,"name":"Chris","username":"ChrisBeach","avatar_template":"/user_avatar/meta.discourse.org/chrisbeach/{size}/214628_2.png","created_at":"2017-10-01T08:18:48.148Z","like_count":1,"blurb":"...from the core team. I propose that on hitting the upgrade button, a new docker image is built in the background, and within it, acceptance tests of all plugins are run before the switch-over happens f...","post_number":1,"topic_title_headline":"Smoke-testing plugins during upgrade process","topic_id":71118},{"id":288991,"name":"David Taylor","username":"david","avatar_template":"/user_avatar/meta.discourse.org/david/{size}/157490_2.png","created_at":"2017-05-16T10:28:58.496Z","like_count":1,"blurb":"I'm trying to use the docker image for tests both on my mac, and also https://meta.discourse.org/t/setting-up-plugin-continuous-integration-tests-on-travis-ci/59612 on travis . For a while now the qun...","post_number":1,"topic_title_headline":"QUnit tests won’t pass in discourse_dev docker image","topic_id":62797},{"id":187533,"name":"Sckott","username":"sckott","avatar_template":"/user_avatar/meta.discourse.org/sckott/{size}/115359_2.png","created_at":"2016-04-21T19:15:00.191Z","like_count":0,"blurb":"What's the best or fastest way to get Discourse installed on Travis for testing a client for the Discourse API ? It appears as though the discourse_api gem uses webmock so I think does not use a real ...","post_number":1,"topic_title_headline":"Testing a Discourse API client on Travis-CI","topic_id":42947},{"id":966756,"name":"Connor Parrish","username":"Connor_Parrish","avatar_template":"/user_avatar/meta.discourse.org/connor_parrish/{size}/225463_2.png","created_at":"2021-07-22T16:57:05.885Z","like_count":1,"blurb":"When you're conditionally adding a PostMenuButton using the plugin-api , the extra button is included in _extraButtons in between acceptance tests. When I run tests, if the tests where the button shou...","post_number":1,"topic_title_headline":"PostMenu’s ‘_extraButtons’ isn’t reset in between acceptance tests","topic_id":197887}],"topics":[{"id":49167,"title":"Write acceptance tests and component tests for Ember code in Discourse","fancy_title":"Write acceptance tests and component tests for Ember code in Discourse","slug":"write-acceptance-tests-and-component-tests-for-ember-code-in-discourse","posts_count":3,"reply_count":1,"highest_post_number":3,"created_at":"2016-08-24T20:48:02.492Z","last_posted_at":"2017-02-01T18:22:01.859Z","bumped":true,"bumped_at":"2017-02-01T18:22:01.859Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["tutorial","ember","testing"],"tags_descriptions":{},"category_id":56,"has_accepted_answer":false},{"id":32619,"title":"Developing Discourse Plugins - Part 6 - Add acceptance tests","fancy_title":"Developing Discourse Plugins - Part 6 - Add acceptance tests","slug":"developing-discourse-plugins-part-6-add-acceptance-tests","posts_count":33,"reply_count":26,"highest_post_number":38,"created_at":"2015-08-27T21:32:26.323Z","last_posted_at":"2022-06-02T11:06:38.274Z","bumped":true,"bumped_at":"2022-06-02T11:06:38.274Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["plugins","tutorial","plugin-guides","testing"],"tags_descriptions":{"plugins":""},"category_id":56,"has_accepted_answer":false},{"id":281579,"title":"End-to-end system testing for themes and theme components","fancy_title":"End-to-end system testing for themes and theme components","slug":"end-to-end-system-testing-for-themes-and-theme-components","posts_count":1,"reply_count":0,"highest_post_number":1,"created_at":"2023-10-24T23:13:37.118Z","last_posted_at":"2023-10-24T23:13:37.118Z","bumped":true,"bumped_at":"2023-11-13T23:20:01.596Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["how-to","themes"],"tags_descriptions":{"how-to":"How to guides contain steps to follow to solve a specific problem"},"category_id":56,"has_accepted_answer":false},{"id":66857,"title":"How to run Discourse core, plugin and theme QUnit test suites","fancy_title":"How to run Discourse core, plugin and theme QUnit test suites","slug":"how-to-run-discourse-core-plugin-and-theme-qunit-test-suites","posts_count":1,"reply_count":2,"highest_post_number":1,"created_at":"2017-07-26T14:09:58.032Z","last_posted_at":"2017-07-26T14:09:58.126Z","bumped":true,"bumped_at":"2023-09-04T17:56:33.079Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["how-to"],"tags_descriptions":{"how-to":"How to guides contain steps to follow to solve a specific problem"},"category_id":56,"has_accepted_answer":false},{"id":17155,"title":"Test Discourse in mobile screen emulator","fancy_title":"Test Discourse in mobile screen emulator","slug":"test-discourse-in-mobile-screen-emulator","posts_count":3,"reply_count":1,"highest_post_number":3,"created_at":"2014-07-03T11:45:09.360Z","last_posted_at":"2014-10-12T22:19:24.137Z","bumped":true,"bumped_at":"2014-10-12T22:19:24.137Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["how-to"],"tags_descriptions":{"how-to":"How to guides contain steps to follow to solve a specific problem"},"category_id":56,"has_accepted_answer":false},{"id":288363,"title":"Tip: when testing inbound email with fake user accounts...","fancy_title":"Tip: when testing inbound email with fake user accounts…","slug":"tip-when-testing-inbound-email-with-fake-user-accounts","posts_count":1,"reply_count":0,"highest_post_number":1,"created_at":"2023-12-12T10:51:07.868Z","last_posted_at":"2023-12-12T10:51:08.401Z","bumped":true,"bumped_at":"2023-12-12T10:51:08.401Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["email"],"tags_descriptions":{},"category_id":55,"has_accepted_answer":false},{"id":145744,"title":"Generate User API Keys for testing","fancy_title":"Generate User API Keys for testing","slug":"generate-user-api-keys-for-testing","posts_count":4,"reply_count":1,"highest_post_number":4,"created_at":"2020-03-26T21:31:38.269Z","last_posted_at":"2022-08-07T15:27:18.788Z","bumped":true,"bumped_at":"2022-08-07T15:27:18.788Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["how-to"],"tags_descriptions":{"how-to":"How to guides contain steps to follow to solve a specific problem"},"category_id":56,"has_accepted_answer":false},{"id":58298,"title":"Build a sandbox to test changes before making them live","fancy_title":"Build a sandbox to test changes before making them live","slug":"build-a-sandbox-to-test-changes-before-making-them-live","posts_count":21,"reply_count":13,"highest_post_number":21,"created_at":"2017-03-03T13:34:07.921Z","last_posted_at":"2022-02-16T23:18:54.680Z","bumped":true,"bumped_at":"2022-02-16T23:18:54.680Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["how-to"],"tags_descriptions":{"how-to":"How to guides contain steps to follow to solve a specific problem"},"category_id":55,"has_accepted_answer":false},{"id":118296,"title":"Migrate from tests-passed to stable","fancy_title":"Migrate from tests-passed to stable","slug":"migrate-from-tests-passed-to-stable","posts_count":12,"reply_count":7,"highest_post_number":12,"created_at":"2019-05-21T10:42:51.017Z","last_posted_at":"2019-06-21T15:55:53.072Z","bumped":true,"bumped_at":"2019-05-22T15:55:47.651Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":288705,"title":"502 Bad Gateway after online rebuild of tests-passed Production just now","fancy_title":"502 Bad Gateway after online rebuild of tests-passed Production just now","slug":"502-bad-gateway-after-online-rebuild-of-tests-passed-production-just-now","posts_count":6,"reply_count":1,"highest_post_number":6,"created_at":"2023-12-14T17:11:26.387Z","last_posted_at":"2024-01-13T17:37:27.535Z","bumped":true,"bumped_at":"2023-12-14T17:36:57.066Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":247546,"title":"Trouble on adding a simple unit test for Youtube oneboxing","fancy_title":"Trouble on adding a simple unit test for Youtube oneboxing","slug":"trouble-on-adding-a-simple-unit-test-for-youtube-oneboxing","posts_count":10,"reply_count":5,"highest_post_number":10,"created_at":"2022-12-02T20:49:55.713Z","last_posted_at":"2023-01-05T17:47:40.629Z","bumped":true,"bumped_at":"2022-12-06T17:47:28.771Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":["onebox","testing"],"tags_descriptions":{},"category_id":7,"has_accepted_answer":true},{"id":274165,"title":"Strange QUnit behaviour?: test failing because setting value doesn't survive","fancy_title":"Strange QUnit behaviour?: test failing because setting value doesn’t survive","slug":"strange-qunit-behaviour-test-failing-because-setting-value-doesnt-survive","posts_count":5,"reply_count":2,"highest_post_number":5,"created_at":"2023-08-07T10:05:22.298Z","last_posted_at":"2023-09-06T11:19:24.509Z","bumped":true,"bumped_at":"2023-08-07T11:24:27.150Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":true},{"id":249167,"title":"Is it possible to override the Site object with own fixture during Front End tests of a Plugin?","fancy_title":"Is it possible to override the Site object with own fixture during Front End tests of a Plugin?","slug":"is-it-possible-to-override-the-site-object-with-own-fixture-during-front-end-tests-of-a-plugin","posts_count":4,"reply_count":0,"highest_post_number":4,"created_at":"2022-12-16T18:32:29.079Z","last_posted_at":"2022-12-28T21:57:56.070Z","bumped":true,"bumped_at":"2022-12-28T21:57:56.070Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":true},{"id":286355,"title":"Acceptance tests failing on Github Actions","fancy_title":"Acceptance tests failing on Github Actions","slug":"acceptance-tests-failing-on-github-actions","posts_count":6,"reply_count":0,"highest_post_number":6,"created_at":"2023-11-22T18:46:46.128Z","last_posted_at":"2023-11-24T13:59:40.100Z","bumped":true,"bumped_at":"2023-11-24T13:59:40.100Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":true},{"id":91312,"title":"There was a problem sending the test email","fancy_title":"There was a problem sending the test email","slug":"there-was-a-problem-sending-the-test-email","posts_count":8,"reply_count":5,"highest_post_number":8,"created_at":"2018-07-01T17:12:48.366Z","last_posted_at":"2018-08-01T09:28:21.935Z","bumped":true,"bumped_at":"2018-07-02T09:28:16.014Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":287022,"title":"Strange migration error in tests during GH workflow","fancy_title":"Strange migration error in tests during GH workflow","slug":"strange-migration-error-in-tests-during-gh-workflow","posts_count":6,"reply_count":3,"highest_post_number":6,"created_at":"2023-11-29T23:18:11.686Z","last_posted_at":"2023-12-31T15:09:05.633Z","bumped":true,"bumped_at":"2023-12-01T15:08:31.257Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":true},{"id":238372,"title":"Smtp doctor test using port 465 even though its configured to use 2525","fancy_title":"Smtp doctor test using port 465 even though its configured to use 2525","slug":"smtp-doctor-test-using-port-465-even-though-its-configured-to-use-2525","posts_count":12,"reply_count":7,"highest_post_number":12,"created_at":"2022-09-07T20:22:10.976Z","last_posted_at":"2022-10-09T00:21:55.656Z","bumped":true,"bumped_at":"2022-09-09T00:21:48.272Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":146326,"title":"Sidekiq not running. Sidekiq heartbeat test failed, restarting","fancy_title":"Sidekiq not running. Sidekiq heartbeat test failed, restarting","slug":"sidekiq-not-running-sidekiq-heartbeat-test-failed-restarting","posts_count":16,"reply_count":9,"highest_post_number":16,"created_at":"2020-03-31T18:51:44.061Z","last_posted_at":"2020-06-10T01:39:28.366Z","bumped":true,"bumped_at":"2020-05-11T01:39:26.054Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":["unsupported-install"],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":255406,"title":"Tests for plugin that requires a plugin","fancy_title":"Tests for plugin that requires a plugin","slug":"tests-for-plugin-that-requires-a-plugin","posts_count":3,"reply_count":0,"highest_post_number":3,"created_at":"2023-02-16T22:12:10.344Z","last_posted_at":"2023-02-28T16:10:36.498Z","bumped":true,"bumped_at":"2023-02-28T20:21:06.122Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":true},{"id":246069,"title":"Need to restart Ember in order to test front-end changes","fancy_title":"Need to restart Ember in order to test front-end changes","slug":"need-to-restart-ember-in-order-to-test-front-end-changes","posts_count":5,"reply_count":1,"highest_post_number":5,"created_at":"2022-11-18T17:03:45.900Z","last_posted_at":"2022-11-18T18:17:53.648Z","bumped":true,"bumped_at":"2022-11-18T18:17:53.648Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":true},{"id":227090,"title":"Bitnami Discourse VM on Virtualbox + SMTP mail-server for testing","fancy_title":"Bitnami Discourse VM on Virtualbox + SMTP mail-server for testing","slug":"bitnami-discourse-vm-on-virtualbox-smtp-mail-server-for-testing","posts_count":3,"reply_count":0,"highest_post_number":3,"created_at":"2022-05-15T18:02:33.136Z","last_posted_at":"2022-05-16T09:10:43.794Z","bumped":true,"bumped_at":"2022-05-16T09:10:43.794Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":["unsupported-install"],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":145781,"title":"Test emails sent but","fancy_title":"Test emails sent but","slug":"test-emails-sent-but","posts_count":5,"reply_count":2,"highest_post_number":5,"created_at":"2020-03-27T07:14:10.116Z","last_posted_at":"2020-04-29T02:52:31.927Z","bumped":true,"bumped_at":"2020-03-30T02:52:29.062Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":137496,"title":"Sidekiq heartbeat test failed, restarting","fancy_title":"Sidekiq heartbeat test failed, restarting","slug":"sidekiq-heartbeat-test-failed-restarting","posts_count":13,"reply_count":8,"highest_post_number":13,"created_at":"2020-01-01T11:20:33.492Z","last_posted_at":"2020-02-11T23:09:42.375Z","bumped":true,"bumped_at":"2020-01-12T23:09:39.730Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":212684,"title":"MailGun & Discourse: Sandbox subdomains are for test purposes only. Please add your own domain","fancy_title":"MailGun & Discourse: Sandbox subdomains are for test purposes only. Please add your own domain","slug":"mailgun-discourse-sandbox-subdomains-are-for-test-purposes-only-please-add-your-own-domain","posts_count":4,"reply_count":2,"highest_post_number":5,"created_at":"2021-12-20T13:52:10.405Z","last_posted_at":"2022-01-19T15:27:11.368Z","bumped":true,"bumped_at":"2021-12-20T15:26:24.911Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":102035,"title":"Test admin features without having to install Discourse","fancy_title":"Test admin features without having to install Discourse","slug":"test-admin-features-without-having-to-install-discourse","posts_count":6,"reply_count":2,"highest_post_number":6,"created_at":"2018-11-14T15:20:59.484Z","last_posted_at":"2021-09-09T07:35:18.270Z","bumped":true,"bumped_at":"2021-09-09T07:35:18.270Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":121299,"title":"Install discourse with a staging (test) ssl certificate","fancy_title":"Install discourse with a staging (test) ssl certificate","slug":"install-discourse-with-a-staging-test-ssl-certificate","posts_count":5,"reply_count":1,"highest_post_number":5,"created_at":"2019-06-25T17:12:34.552Z","last_posted_at":"2023-04-01T03:25:32.925Z","bumped":true,"bumped_at":"2019-09-04T15:15:31.153Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":118568,"title":"Problem testing Badge Query from Data Explorer","fancy_title":"Problem testing Badge Query from Data Explorer","slug":"problem-testing-badge-query-from-data-explorer","posts_count":5,"reply_count":1,"highest_post_number":6,"created_at":"2019-05-24T00:14:35.814Z","last_posted_at":"2019-05-24T00:46:42.560Z","bumped":true,"bumped_at":"2019-05-24T00:46:42.560Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":["sql-triggered-badge"],"tags_descriptions":{"sql-triggered-badge":"SQL queries for custom triggered badges"},"category_id":148,"has_accepted_answer":true},{"id":115912,"title":"New iOS mobile app beta available for testing","fancy_title":"New iOS mobile app beta available for testing","slug":"new-ios-mobile-app-beta-available-for-testing","posts_count":49,"reply_count":31,"highest_post_number":49,"created_at":"2019-04-25T01:24:56.608Z","last_posted_at":"2019-05-31T17:31:02.516Z","bumped":true,"bumped_at":"2020-01-21T17:07:37.817Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":224560,"title":"502 Bad Gateway after trying to rebuild test-passed branch","fancy_title":"502 Bad Gateway after trying to rebuild test-passed branch","slug":"502-bad-gateway-after-trying-to-rebuild-test-passed-branch","posts_count":6,"reply_count":1,"highest_post_number":6,"created_at":"2022-04-17T21:46:04.598Z","last_posted_at":"2022-04-17T22:15:37.255Z","bumped":true,"bumped_at":"2022-04-17T22:15:37.255Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":false},{"id":173019,"title":"Forum offline due to failed rebuilds on Tests-Pass","fancy_title":"Forum offline due to failed rebuilds on Tests-Pass","slug":"forum-offline-due-to-failed-rebuilds-on-tests-pass","posts_count":2,"reply_count":0,"highest_post_number":2,"created_at":"2020-12-11T18:44:17.952Z","last_posted_at":"2020-12-11T19:12:11.141Z","bumped":true,"bumped_at":"2020-12-11T19:12:11.141Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":false},{"id":196501,"title":"[A/B Testing] Changing parent CSS class based on experiment variable","fancy_title":"[A/B Testing] Changing parent CSS class based on experiment variable","slug":"a-b-testing-changing-parent-css-class-based-on-experiment-variable","posts_count":3,"reply_count":1,"highest_post_number":3,"created_at":"2021-07-08T19:58:55.503Z","last_posted_at":"2021-07-15T18:19:21.092Z","bumped":true,"bumped_at":"2021-07-15T18:19:21.092Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":282856,"title":"Code-level performance testing","fancy_title":"Code-level performance testing","slug":"code-level-performance-testing","posts_count":2,"reply_count":0,"highest_post_number":2,"created_at":"2023-10-20T03:44:49.568Z","last_posted_at":"2023-10-23T23:29:59.741Z","bumped":true,"bumped_at":"2023-10-23T23:29:59.741Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":272112,"title":"Running core tests in docker environment","fancy_title":"Running core tests in docker environment","slug":"running-core-tests-in-docker-environment","posts_count":1,"reply_count":0,"highest_post_number":1,"created_at":"2023-07-19T12:45:57.266Z","last_posted_at":"2023-07-19T12:45:57.446Z","bumped":true,"bumped_at":"2023-07-19T12:45:57.446Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["docker","spec","testing"],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":87295,"title":"Email test from the console?","fancy_title":"Email test from the console?","slug":"email-test-from-the-console","posts_count":4,"reply_count":1,"highest_post_number":4,"created_at":"2018-05-11T22:03:23.101Z","last_posted_at":"2018-05-12T00:55:41.843Z","bumped":true,"bumped_at":"2018-05-12T00:55:41.843Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":59577,"title":"Plugin QUnit tests are not running as part of rake qunit:test","fancy_title":"Plugin QUnit tests are not running as part of rake qunit:test","slug":"plugin-qunit-tests-are-not-running-as-part-of-rake-qunit-test","posts_count":8,"reply_count":5,"highest_post_number":8,"created_at":"2017-03-20T20:29:06.536Z","last_posted_at":"2017-07-17T18:26:45.188Z","bumped":true,"bumped_at":"2017-07-17T18:26:45.188Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":181856,"title":"How does the Discourse team do usability testing?","fancy_title":"How does the Discourse team do usability testing?","slug":"how-does-the-discourse-team-do-usability-testing","posts_count":5,"reply_count":2,"highest_post_number":6,"created_at":"2021-03-03T18:09:29.357Z","last_posted_at":"2021-03-04T15:23:46.571Z","bumped":true,"bumped_at":"2021-03-04T15:23:46.571Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":3,"has_accepted_answer":false},{"id":108110,"title":"Advice on writing Ruby tests for plugins","fancy_title":"Advice on writing Ruby tests for plugins","slug":"advice-on-writing-ruby-tests-for-plugins","posts_count":15,"reply_count":13,"highest_post_number":15,"created_at":"2019-02-01T09:52:27.051Z","last_posted_at":"2019-05-03T03:54:47.163Z","bumped":true,"bumped_at":"2019-05-03T03:54:47.163Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":57292,"title":"Acceptance test content is present on page","fancy_title":"Acceptance test content is present on page","slug":"acceptance-test-content-is-present-on-page","posts_count":7,"reply_count":3,"highest_post_number":7,"created_at":"2017-02-13T02:48:10.108Z","last_posted_at":"2017-02-14T05:55:31.520Z","bumped":true,"bumped_at":"2017-02-14T05:55:31.520Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":291039,"title":"Google Search Console/Schema Markup test tool \"Google Rich Results Test\": mobile-view instead of crawler-view","fancy_title":"Google Search Console/Schema Markup test tool “Google Rich Results Test”: mobile-view instead of crawler-view","slug":"google-search-console-schema-markup-test-tool-google-rich-results-test-mobile-view-instead-of-crawler-view","posts_count":3,"reply_count":1,"highest_post_number":3,"created_at":"2024-01-09T16:53:57.797Z","last_posted_at":"2024-01-09T17:17:09.039Z","bumped":true,"bumped_at":"2024-01-09T17:17:09.039Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":1,"has_accepted_answer":false},{"id":267737,"title":"Experiments with Model-Based Testing","fancy_title":"Experiments with Model-Based Testing","slug":"experiments-with-model-based-testing","posts_count":1,"reply_count":0,"highest_post_number":1,"created_at":"2023-06-08T22:35:51.784Z","last_posted_at":"2023-06-08T22:35:51.914Z","bumped":true,"bumped_at":"2023-06-08T22:35:51.914Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":233389,"title":"Qunit tests not deterministic in Plugin?","fancy_title":"Qunit tests not deterministic in Plugin?","slug":"qunit-tests-not-deterministic-in-plugin","posts_count":2,"reply_count":0,"highest_post_number":2,"created_at":"2022-07-20T08:48:28.757Z","last_posted_at":"2022-07-20T11:03:42.522Z","bumped":true,"bumped_at":"2022-07-20T11:03:42.522Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":135876,"title":"Issues migrating test database","fancy_title":"Issues migrating test database","slug":"issues-migrating-test-database","posts_count":9,"reply_count":1,"highest_post_number":9,"created_at":"2019-12-12T21:01:31.303Z","last_posted_at":"2021-03-02T16:44:20.120Z","bumped":true,"bumped_at":"2021-03-02T16:44:20.120Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":175413,"title":"Acceptance Test for Markdown Extension?","fancy_title":"Acceptance Test for Markdown Extension?","slug":"acceptance-test-for-markdown-extension","posts_count":3,"reply_count":0,"highest_post_number":4,"created_at":"2021-01-07T19:56:04.931Z","last_posted_at":"2021-01-10T11:26:16.888Z","bumped":true,"bumped_at":"2021-01-10T16:30:56.164Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":150786,"title":"Auto migrate test database schema","fancy_title":"Auto migrate test database schema","slug":"auto-migrate-test-database-schema","posts_count":2,"reply_count":0,"highest_post_number":2,"created_at":"2020-05-08T04:27:56.739Z","last_posted_at":"2020-05-08T13:15:45.247Z","bumped":true,"bumped_at":"2020-05-08T13:15:45.247Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":118639,"title":"Building Tests for New discourse_api Endpoints","fancy_title":"Building Tests for New discourse_api Endpoints","slug":"building-tests-for-new-discourse-api-endpoints","posts_count":20,"reply_count":9,"highest_post_number":21,"created_at":"2019-05-24T22:21:24.896Z","last_posted_at":"2019-10-02T21:13:35.140Z","bumped":true,"bumped_at":"2019-10-02T21:36:26.639Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":126025,"title":"Seed or API calls to create test users","fancy_title":"Seed or API calls to create test users","slug":"seed-or-api-calls-to-create-test-users","posts_count":7,"reply_count":5,"highest_post_number":7,"created_at":"2019-08-17T02:13:06.578Z","last_posted_at":"2019-08-19T11:50:36.137Z","bumped":true,"bumped_at":"2019-08-19T11:50:36.137Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":71118,"title":"Smoke-testing plugins during upgrade process","fancy_title":"Smoke-testing plugins during upgrade process","slug":"smoke-testing-plugins-during-upgrade-process","posts_count":22,"reply_count":17,"highest_post_number":22,"created_at":"2017-10-01T08:18:48.067Z","last_posted_at":"2017-10-02T23:44:59.852Z","bumped":true,"bumped_at":"2017-10-02T23:44:59.852Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["pr-welcome"],"tags_descriptions":{"pr-welcome":"You're welcome to submit a Github pull request that implements this"},"category_id":2,"has_accepted_answer":false},{"id":62797,"title":"QUnit tests won't pass in discourse_dev docker image","fancy_title":"QUnit tests won’t pass in discourse_dev docker image","slug":"qunit-tests-wont-pass-in-discourse-dev-docker-image","posts_count":20,"reply_count":11,"highest_post_number":21,"created_at":"2017-05-16T10:28:58.397Z","last_posted_at":"2017-07-25T15:50:27.716Z","bumped":true,"bumped_at":"2017-07-25T15:50:27.716Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":42947,"title":"Testing a Discourse API client on Travis-CI","fancy_title":"Testing a Discourse API client on Travis-CI","slug":"testing-a-discourse-api-client-on-travis-ci","posts_count":5,"reply_count":3,"highest_post_number":5,"created_at":"2016-04-21T19:15:00.130Z","last_posted_at":"2016-04-22T03:03:47.976Z","bumped":true,"bumped_at":"2016-04-22T03:03:47.976Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":197887,"title":"PostMenu's '_extraButtons' isn't reset in between acceptance tests","fancy_title":"PostMenu’s ‘_extraButtons’ isn’t reset in between acceptance tests","slug":"postmenus-extrabuttons-isnt-reset-in-between-acceptance-tests","posts_count":2,"reply_count":0,"highest_post_number":4,"created_at":"2021-07-22T16:57:05.803Z","last_posted_at":"2021-08-04T09:11:29.538Z","bumped":true,"bumped_at":"2021-08-04T09:11:29.538Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false}],"users":[],"categories":[],"tags":[],"groups":[],"grouped_search_result":{"more_posts":null,"more_users":null,"more_categories":null,"term":"testing","search_log_id":2089836,"more_full_page_results":true,"can_create_topic":false,"error":null,"post_ids":[218354,138484,1381521,311252,59431,1419473,722424,266441,582008,1421414,1204506,1339808,1211823,1408389,443129,1412219,1160803,725228,1240758,1197679,1103913,722608,679950,1033333,498559,596557,583541,569209,1090520,860593,960683,1389188,1329017,421687,272436,899205,530438,260691,1432567,1305996,1135474,672801,871105,747613,583961,621707,334186,288991,187533,966756],"user_ids":[],"category_ids":[],"tag_ids":[],"group_ids":[],"extra":{"categories":[{"id":67,"name":"announcements","color":"ED207B","text_color":"FFFFFF","slug":"announcements","topic_count":312,"post_count":3973,"position":0,"description":"The place for all Discourse announcements.","description_text":"The place for all Discourse announcements.","description_excerpt":"The place for all Discourse announcements.","topic_url":"/t/about-the-announcements-category/68629","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":true,"sort_order":null,"sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":true,"custom_fields":{"has_chat_enabled":null,"activity_pub_ready":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_enabled":true,"activity_pub_username":"announcements","activity_pub_name":"Announcements","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":["new-feature","security"],"allowed_tag_groups":[],"allow_global_tags":true,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":true,"activity_pub_ready":true,"activity_pub_actor":{"id":2,"handle":"announcements@meta.discourse.org","name":"Announcements"},"activity_pub_username":"announcements","activity_pub_name":"Announcements","activity_pub_default_visibility":"public","activity_pub_publication_type":"first_post","activity_pub_post_object_type":"Note","uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":13,"name":"blog","color":"8080ff","text_color":"FFFFFF","slug":"blog","topic_count":170,"post_count":1477,"position":1,"description":"Discussion topics generated from the official Discourse Blog. These topics are linked from the bottom of each blog entry where the blog comments would normally be.","description_text":"Discussion topics generated from the official Discourse Blog. These topics are linked from the bottom of each blog entry where the blog comments would normally be.","description_excerpt":"Discussion topics generated from the official Discourse Blog. These topics are linked from the bottom of each blog entry where the blog comments would normally be.","topic_url":"/t/about-the-blog-category/5250","read_restricted":false,"permission":null,"parent_category_id":67,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"created","sort_ascending":false,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":10,"name":"documentation","color":"00A94F","text_color":"FFFFFF","slug":"documentation","topic_count":2,"post_count":2,"position":2,"description":"Documentation for how to use Discourse, install and configure sites, and troubleshoot common issues. Includes topics for tutorials, how-tos, general reference and troubleshooting. Topics need to be created in one of the subcategories, and may only be created by trust level 2 and up.","description_text":"Documentation for how to use Discourse, install and configure sites, and troubleshoot common issues. Includes topics for tutorials, how-tos, general reference and troubleshooting. Topics need to be created in one of the subcategories, and may only be created by trust level 2 and up.","description_excerpt":"Documentation for how to use Discourse, install and configure sites, and troubleshoot common issues. Includes topics for tutorials, how-tos, general reference and troubleshooting. Topics need to be created in one of the subcategories, and may only be created by trust level 2 and up.","topic_url":"/t/about-the-documentation-category/2629","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":true,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"top","subcategory_list_style":"boxes","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":true,"custom_fields":{"has_chat_enabled":null,"activity_pub_ready":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Article","activity_pub_publication_type":"full_topic","activity_pub_username":"documentation","activity_pub_enabled":true,"activity_pub_name":"Documentation"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":true,"activity_pub_ready":true,"activity_pub_actor":{"id":31769,"handle":"documentation@meta.discourse.org","name":"Documentation"},"activity_pub_username":"documentation","activity_pub_name":"Documentation","activity_pub_default_visibility":"public","activity_pub_publication_type":"full_topic","activity_pub_post_object_type":"Article","uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":53,"name":"admins","color":"F15D22","text_color":"FFFFFF","slug":"admins","topic_count":222,"post_count":1703,"position":3,"description":"Guides for Discourse admins and community managers with admin access.","description_text":"Guides for Discourse admins and community managers with admin access.","description_excerpt":"Guides for Discourse admins and community managers with admin access.","topic_url":"/t/about-the-admins-category/56207","read_restricted":false,"permission":null,"parent_category_id":10,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":true,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":["hosted-support","migrations"],"allowed_tag_groups":[],"allow_global_tags":true,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":125,"name":"moderators","color":"40d0e2","text_color":"FFFFFF","slug":"moderators","topic_count":16,"post_count":71,"position":4,"description":"Documentation for moderators and community managers using Discourse.","description_text":"Documentation for moderators and community managers using Discourse.","description_excerpt":"Documentation for moderators and community managers using Discourse.","topic_url":"/t/about-the-moderators-category/238588","read_restricted":false,"permission":null,"parent_category_id":10,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":126,"name":"users","color":"0088CC","text_color":"FFFFFF","slug":"users","topic_count":53,"post_count":184,"position":5,"description":"Documentation for all members of communities running Discourse.","description_text":"Documentation for all members of communities running Discourse.","description_excerpt":"Documentation for all members of communities running Discourse.","topic_url":"/t/about-the-users-category/238917","read_restricted":false,"permission":null,"parent_category_id":10,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":55,"name":"sysadmin","color":"E9DD00","text_color":"FFFFFF","slug":"sysadmin","topic_count":175,"post_count":2803,"position":6,"description":"Documentation for self-hosters and Discourse system administrators.","description_text":"Documentation for self-hosters and Discourse system administrators.","description_excerpt":"Documentation for self-hosters and Discourse system administrators.","topic_url":"/t/about-the-sysadmin-category/56209","read_restricted":false,"permission":null,"parent_category_id":10,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":true,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":["migrations"],"allowed_tag_groups":[],"allow_global_tags":true,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":127,"name":"theme developers","color":"92278F","text_color":"FFFFFF","slug":"theme-developers","topic_count":41,"post_count":215,"position":7,"description":"Documentation for developing themes and components that can be installed by admins.","description_text":"Documentation for developing themes and components that can be installed by admins.","description_excerpt":"Documentation for developing themes and components that can be installed by admins.","topic_url":"/t/about-the-theme-developers-category/239285","read_restricted":false,"permission":null,"parent_category_id":10,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":56,"name":"developers","color":"00A94F","text_color":"FFFFFF","slug":"devs","topic_count":96,"post_count":1185,"position":8,"description":"Documentation for developing features, plugins, or integrations with Discourse.","description_text":"Documentation for developing features, plugins, or integrations with Discourse.","description_excerpt":"Documentation for developing features, plugins, or integrations with Discourse.","topic_url":"/t/about-the-developers-category/56210","read_restricted":false,"permission":null,"parent_category_id":10,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":true,"custom_fields":{"has_chat_enabled":null,"activity_pub_ready":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Article","activity_pub_enabled":true,"activity_pub_username":"developer-docs","activity_pub_name":"Developer Documentation","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":true,"activity_pub_ready":true,"activity_pub_actor":{"id":35545,"handle":"developer-docs@meta.discourse.org","name":"Developer Documentation"},"activity_pub_username":"developer-docs","activity_pub_name":"Developer Documentation","activity_pub_default_visibility":"public","activity_pub_publication_type":"first_post","activity_pub_post_object_type":"Article","uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":6,"name":"support","color":"CEA9A9","text_color":"FFFFFF","slug":"support","topic_count":15841,"post_count":103687,"position":9,"description":"The category for general support questions on using your Discourse site.","description_text":"The category for general support questions on using your Discourse site.","description_excerpt":"The category for general support questions on using your Discourse site.","topic_url":"/t/about-the-support-category/389","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"\u003e Before asking, did you search first? Press 🔍 at the upper right to search.","has_children":true,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_accepted_answers":"true","enable_unassigned_filter":"false","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":21,"name":"wordpress","color":"F9CFCF","text_color":"FFFFFF","slug":"wordpress","topic_count":732,"post_count":5110,"position":10,"description":"Support for the official Discourse WordPress plugin at \u003ca href=\"https://github.com/discourse/wp-discourse\" class=\"inline-onebox\"\u003eGitHub - discourse/wp-discourse: WordPress plugin that lets you use Discourse as the community engine for a WordPress blog\u003c/a\u003e","description_text":"Support for the official Discourse WordPress plugin at GitHub - discourse/wp-discourse: WordPress plugin that lets you use Discourse as the community engine for a WordPress blog","description_excerpt":"Support for the official Discourse WordPress plugin at \u003ca href=\"https://github.com/discourse/wp-discourse\" class=\"inline-onebox\"\u003eGitHub - discourse/wp-discourse: WordPress plugin that lets you use Discourse as the community engine for a WordPress blog\u003c/a\u003e","topic_url":"/t/about-the-wordpress-category/12282","read_restricted":false,"permission":null,"parent_category_id":6,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":"true","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":1,"name":"bug","color":"e9dd00","text_color":"000000","slug":"bug","topic_count":5153,"post_count":35973,"position":11,"description":"A bug report means \u003cstrong\u003esomething is broken, preventing normal/typical use of Discourse\u003c/strong\u003e. Do be sure to search prior to submitting bugs. \u003ca href=\"https://meta.discourse.org/t/how-to-write-a-good-bug-report/183671/\"\u003eInclude repro steps\u003c/a\u003e, and only describe one bug per topic please.","description_text":"A bug report means something is broken, preventing normal/typical use of Discourse. Do be sure to search prior to submitting bugs. Include repro steps, and only describe one bug per topic please.","description_excerpt":"A bug report means something is broken, preventing normal/typical use of Discourse. Do be sure to search prior to submitting bugs. \u003ca href=\"https://meta.discourse.org/t/how-to-write-a-good-bug-report/183671/\"\u003eInclude repro steps\u003c/a\u003e, and only describe one bug per topic please.","topic_url":"/t/about-the-bug-category/2","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_accepted_answers":null,"enable_unassigned_filter":"true","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":true,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":9,"name":"ux","color":"5F497A","text_color":"FFFFFF","slug":"ux","topic_count":2700,"post_count":18225,"position":12,"description":"Discussion about the user interface of Discourse and how features are presented (including language and UI elements).","description_text":"Discussion about the user interface of Discourse and how features are presented (including language and UI elements).","description_excerpt":"Discussion about the user interface of Discourse and how features are presented (including language and UI elements).","topic_url":"/t/about-the-ux-category/2628","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":2,"name":"feature","color":"0E76BD","text_color":"FFFFFF","slug":"feature","topic_count":6997,"post_count":58067,"position":13,"description":"Discussion about existing Discourse features, how they can be improved or enhanced, and how proposed new features could work.","description_text":"Discussion about existing Discourse features, how they can be improved or enhanced, and how proposed new features could work.","description_excerpt":"Discussion about existing Discourse features, how they can be improved or enhanced, and how proposed new features could work.","topic_url":"/t/about-the-feature-category/11","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_ready":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_username":"feature","activity_pub_name":"Feature Requests","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post","activity_pub_enabled":true},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":true,"activity_pub_ready":true,"activity_pub_actor":{"id":1,"handle":"feature@meta.discourse.org","name":"Feature Requests"},"activity_pub_username":"feature","activity_pub_name":"Feature Requests","activity_pub_default_visibility":"public","activity_pub_publication_type":"first_post","activity_pub_post_object_type":"Note","uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":148,"name":"data \u0026 reporting","color":"D24899","text_color":"FFFFFF","slug":"data-reporting","topic_count":631,"post_count":3056,"position":14,"description":"This is the category for everything related to Discourse data and reporting. Here, you can discuss dashboard reports, custom badge queries, data-explorer queries and any other analytic add-ons.","description_text":"This is the category for everything related to Discourse data and reporting. Here, you can discuss dashboard reports, custom badge queries, data-explorer queries and any other analytic add-ons.","description_excerpt":"This is the category for everything related to Discourse data and reporting. Here, you can discuss dashboard reports, custom badge queries, data-explorer queries and any other analytic add-ons.","topic_url":"/t/about-the-data-reporting-category/274664","read_restricted":false,"permission":null,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","enable_accepted_answers":"true","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":24,"name":"sso","color":"d47711","text_color":"FFFFFF","slug":"sso","topic_count":493,"post_count":2546,"position":15,"description":"For queries specifically about SSO (single sign-on) and login using third-party providers (Google, Facebook, GitHub etc). See the \u003ca href=\"https://meta.discourse.org/t/discourseconnect-official-single-sign-on-for-discourse-sso/13045\"\u003eofficial documentation on DiscourseConnect SSO\u003c/a\u003e.","description_text":"For queries specifically about SSO (single sign-on) and login using third-party providers (Google, Facebook, GitHub etc). See the official documentation on DiscourseConnect SSO.","description_excerpt":"For queries specifically about SSO (single sign-on) and login using third-party providers (Google, Facebook, GitHub etc). See the \u003ca href=\"https://meta.discourse.org/t/discourseconnect-official-single-sign-on-for-discourse-sso/13045\"\u003eofficial documentation on DiscourseConnect SSO\u003c/a\u003e.","topic_url":"/t/about-the-sso-category/13110","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":"true","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":65,"name":"community","color":"12A89D","text_color":"FFFFFF","slug":"community","topic_count":866,"post_count":8844,"position":16,"description":"A great platform doesn’t guarantee success. Community building is a science. This category is for discussions about launching, building, growing and managing a thriving community.","description_text":"A great platform doesn’t guarantee success. Community building is a science. This category is for discussions about launching, building, growing and managing a thriving community.","description_excerpt":"A great platform doesn’t guarantee success. Community building is a science. This category is for discussions about launching, building, growing and managing a thriving community.","topic_url":"/t/about-the-community-category/67750","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":7,"name":"dev","color":"292929","text_color":"fff","slug":"dev","topic_count":3467,"post_count":20210,"position":17,"description":"The category for all things Discourse Development. Building a customization for yourself or the community? Then this is the category for you!","description_text":"The category for all things Discourse Development. Building a customization for yourself or the community? Then this is the category for you!","description_excerpt":"The category for all things Discourse Development. Building a customization for yourself or the community? Then this is the category for you!","topic_url":"/t/about-the-dev-category/1026","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":true,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"boxes","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":"true","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":27,"name":"translations","color":"808281","text_color":"FFFFFF","slug":"translations","topic_count":297,"post_count":1832,"position":18,"description":"This category is for discussion about localizing Discourse.","description_text":"This category is for discussion about localizing Discourse.","description_excerpt":"This category is for discussion about localizing Discourse.","topic_url":"/t/about-the-translations-category/14549","read_restricted":false,"permission":null,"parent_category_id":7,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":14,"name":"marketplace","color":"8C6238","text_color":"FFFFFF","slug":"marketplace","topic_count":1162,"post_count":5865,"position":19,"description":"This is your hub for all Discourse-related commerce: jobs, gigs, plugins, themes, hosting, and more.","description_text":"This is your hub for all Discourse-related commerce: jobs, gigs, plugins, themes, hosting, and more.","description_excerpt":"This is your hub for all Discourse-related commerce: jobs, gigs, plugins, themes, hosting, and more.","topic_url":"/t/about-the-marketplace-category/5425","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"What would you like done?\n\nWhen do you need it done?\n\nWhat is your budget, in $ USD that you can offer for this task?\n\n\u003c!-- We encourage caution and due diligence when engaging with potential contractors or clients. Verify their credentials, check previous work, and ensure a transparent and legitimate transaction. Always remember, your safety and security in the marketplace is your responsibility. --\u003e","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":["delivered"],"allowed_tag_groups":[],"allow_global_tags":true,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":22,"name":"plugin","color":"F7941D","text_color":"FFFFFF","slug":"plugin","topic_count":315,"post_count":10429,"position":20,"description":"A directory of Discourse plugins, both official and third-party.","description_text":"A directory of Discourse plugins, both official and third-party.","description_excerpt":"A directory of Discourse plugins, both official and third-party.","topic_url":"/t/about-the-plugin-category/12648","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"| | | |\n| - | - | - |\n| :information_source: | **Summary** | In a few words, what does this plugin do? |\n| :hammer_and_wrench: | **Repository Link** | \u003c\u003e |\n| :open_book: | **Install Guide** | [How to install plugins in Discourse](https://meta.discourse.org/t/install-plugins-in-discourse/19157) |\n\n\u003cbr\u003e \n\n### Features\n \nDescribe the major features of the plugin\n \n### Configuration\n \nInclude detailed steps on how to configure the plugin (include screenshots where necessary)\n \n### CHANGELOG\n- Add new bullets when major features are committed here\n \n### TODO\n- Add any #pr-welcome TODO tasks here","has_children":true,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"boxes","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":["Resource Status"],"allow_global_tags":true,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":5,"name":"extras","color":"25AAE2","text_color":"FFFFFF","slug":"extras","topic_count":90,"post_count":985,"position":21,"description":"A directory of all extensions \u0026amp; integrations for Discourse which are \u003cem\u003enot\u003c/em\u003e Discourse plugins, i.e. a CMS plugin, a browser extension or a native application.","description_text":"A directory of all extensions \u0026amp; integrations for Discourse which are not Discourse plugins, i.e. a CMS plugin, a browser extension or a native application.","description_excerpt":"A directory of all extensions \u0026amp; integrations for Discourse which are not Discourse plugins, i.e. a CMS plugin, a browser extension or a native application.","topic_url":"/t/about-the-extras-category/28","read_restricted":false,"permission":null,"parent_category_id":22,"notification_level":1,"topic_template":"","has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":["Resource Status"],"allow_global_tags":true,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":61,"name":"theme","color":"E43D30","text_color":"FFFFFF","slug":"theme","topic_count":66,"post_count":2138,"position":22,"description":"Themes are expansive customizations that change multiple elements of the style of your forum design, and often also include additional front-end features.","description_text":"Themes are expansive customizations that change multiple elements of the style of your forum design, and often also include additional front-end features.","description_excerpt":"Themes are expansive customizations that change multiple elements of the style of your forum design, and often also include additional front-end features.","topic_url":"/t/about-the-theme-category/60925","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"||||\n|-|-|-|\n| :information_source: | **Summary** | ADD SHORT SUMMARY \n| :eyeglasses:|**Preview**| PREVIEW_LINK |\n| :hammer_and_wrench:|**Repository**| REPOSITORY_LINK |\n| :question:|**Install Guide**|[How to install a theme or theme component](https://meta.discourse.org/t/how-do-i-install-a-theme-or-theme-component/63682)|\n| :open_book:|**New to Discourse Themes?**| [Beginner’s guide to using Discourse Themes](https://meta.discourse.org/t/beginners-guide-to-using-discourse-themes/91966)\n\n\u003c!-- Describe this theme in one or two sentences --\u003e\n\nShort description...\n\n\u003c!-- Add screenshots (if applicable) --\u003e\n\nScreenshots...\n\n\u003c!-- Add more details and explain the settings (if applicable) --\u003e\n\nDetailed description...\n","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"none","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":["color-palette"],"allowed_tag_groups":["Resource Status"],"allow_global_tags":true,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":120,"name":"theme-component","color":"1dedf8","text_color":"FFFFFF","slug":"theme-component","topic_count":300,"post_count":7516,"position":23,"description":"Theme components are customizations that change surface elements of your forum design, or add extra front-end features.","description_text":"Theme components are customizations that change surface elements of your forum design, or add extra front-end features.","description_excerpt":"Theme components are customizations that change surface elements of your forum design, or add extra front-end features.","topic_url":"/t/about-the-theme-component-category/232731","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"| | | |\n| - | - | - |\n| :information_source: | **Summary** | ADD SHORT SUMMARY |\n| :eyeglasses: |**Preview**| PREVIEW_LINK |\n| :hammer_and_wrench: | **Repository**| REPOSITORY_LINK |\n| :question: | **Install Guide** | [How to install a theme or theme component](https://meta.discourse.org/t/how-do-i-install-a-theme-or-theme-component/63682) |\n| :open_book: | **New to Discourse Themes?** | [Beginner’s guide to using Discourse Themes](https://meta.discourse.org/t/beginners-guide-to-using-discourse-themes/91966) |\n\n\u003c!-- Fill in \"repoName\" and \"repoURL\" for the automatic install button --\u003e\n\n[wrap=theme-install-button repoName=\"Component's name\" repoUrl=\"GitHub repository link\"]\nInstall this theme component\n[/wrap]\n\n\u003c!-- Describe this theme/component in one or two sentences --\u003e\n\nShort description...\n\n\u003c!-- Add screenshots (if applicable) --\u003e\n\nScreenshots...\n\n\u003c!-- Add more details and explain the settings (if applicable) --\u003e\n\nDetailed description...","has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"none","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":["Resource Status"],"allow_global_tags":true,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":31,"name":"installation","color":"997E7E","text_color":"FFFFFF","slug":"installation","topic_count":3426,"post_count":29611,"position":24,"description":"Getting Discourse up and running, keeping it going, upgrading, and any other general sysadmin maintenance.","description_text":"Getting Discourse up and running, keeping it going, upgrading, and any other general sysadmin maintenance.","description_excerpt":"Getting Discourse up and running, keeping it going, upgrading, and any other general sysadmin maintenance.","topic_url":"/t/about-the-installation-category/21019","read_restricted":false,"permission":null,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":"true","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":106,"name":"migration","color":"652D90","text_color":"FFFFFF","slug":"migration","topic_count":215,"post_count":1558,"position":25,"description":"You want to migrate your community to Discourse? Awesome! This is the category where you can ask questions, get help, and document your Discourse migration journey.","description_text":"You want to migrate your community to Discourse? Awesome! This is the category where you can ask questions, get help, and document your Discourse migration journey.","description_excerpt":"You want to migrate your community to Discourse? Awesome! This is the category where you can ask questions, get help, and document your Discourse migration journey.","topic_url":"/t/about-the-migration-category/196969","read_restricted":false,"permission":null,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":"true","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":["Migration"],"allow_global_tags":true,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":8,"name":"hosting","color":"00AEEF","text_color":"FFFFFF","slug":"hosting","topic_count":501,"post_count":4117,"position":26,"description":"Topics about hosting Discourse, either on your own servers, in the cloud, or with specific hosting services.","description_text":"Topics about hosting Discourse, either on your own servers, in the cloud, or with specific hosting services.","description_excerpt":"Topics about hosting Discourse, either on your own servers, in the cloud, or with specific hosting services.","topic_url":"/t/about-the-hosting-category/2626","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post","enable_accepted_answers":"true"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":30,"name":"releases","color":"BF1E2E","text_color":"FFFFFF","slug":"releases","topic_count":23,"post_count":104,"position":27,"description":"Outlining each official release of Discourse, and plans for future releases.","description_text":"Outlining each official release of Discourse, and plans for future releases.","description_excerpt":"Outlining each official release of Discourse, and plans for future releases.","topic_url":"/t/about-the-releases-category/20857","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"created","sort_ascending":false,"show_subcategory_list":true,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":3,"name":"site feedback","color":"888","text_color":"FFFFFF","slug":"site-feedback","topic_count":417,"post_count":3220,"position":28,"description":"Discussion about \u003ca href=\"http://meta.discourse.org\"\u003emeta.discourse.org\u003c/a\u003e itself - the organization of this forum, how it works, and how we can improve this site.","description_text":"Discussion about meta.discourse.org itself - the organization of this forum, how it works, and how we can improve this site.","description_excerpt":"Discussion about \u003ca href=\"http://meta.discourse.org\"\u003emeta.discourse.org\u003c/a\u003e itself - the organization of this forum, how it works, and how we can improve this site.","topic_url":"/t/about-the-site-feedback-category/24","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":true,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":152,"name":"theme feedback","color":"ED207B","text_color":"FFFFFF","slug":"theme-feedback","topic_count":11,"post_count":44,"position":29,"description":"This is the category to gather all the UX reports for \u003ca href=\"https://meta.discourse.org/t/we-have-a-new-default-theme-here-on-meta/284692\"\u003eour new theme on meta\u003c/a\u003e. It also uses the new Form Templates feature that I’ve been wanting to try out.","description_text":"This is the category to gather all the UX reports for our new theme on meta. It also uses the new Form Templates feature that I’ve been wanting to try out.","description_excerpt":"This is the category to gather all the UX reports for \u003ca href=\"https://meta.discourse.org/t/we-have-a-new-default-theme-here-on-meta/284692\"\u003eour new theme on meta\u003c/a\u003e. It also uses the new Form Templates feature that I’ve been wanting to try out.","topic_url":"/t/about-the-theme-feedback-category/284904","read_restricted":false,"permission":null,"parent_category_id":3,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[1],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":157,"name":"forum summaries","color":"72e9a7","text_color":"FFFFFF","slug":"forum-summaries","topic_count":4,"post_count":36,"position":30,"description":"Stay up-to-date with the pulse of our community through AI-crafted summaries. This category harnesses the power of artificial intelligence to collate and condense forum activities, providing you with comprehensive yet succinct overviews. From emerging discussions to trending topics, our AI summaries deliver the essence of the Discourse community’s heartbeat, right at your fingertips.","description_text":"Stay up-to-date with the pulse of our community through AI-crafted summaries. This category harnesses the power of artificial intelligence to collate and condense forum activities, providing you with comprehensive yet succinct overviews. From emerging discussions to trending topics, our AI summaries deliver the essence of the Discourse community’s heartbeat, right at your fingertips.","description_excerpt":"Stay up-to-date with the pulse of our community through AI-crafted summaries. This category harnesses the power of artificial intelligence to collate and condense forum activities, providing you with comprehensive yet succinct overviews. From emerging discussions to trending topics, our AI summarie\u0026hellip;","topic_url":"/t/about-the-forum-summaries-category/291765","read_restricted":false,"permission":null,"parent_category_id":3,"notification_level":0,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":35,"name":"praise","color":"9EB83B","text_color":"FFFFFF","slug":"praise","topic_count":294,"post_count":1091,"position":31,"description":"Have something nice to say about Discourse?","description_text":"Have something nice to say about Discourse?","description_excerpt":"Have something nice to say about Discourse?","topic_url":"/t/about-the-praise-category/30010","read_restricted":false,"permission":null,"notification_level":1,"topic_template":null,"has_children":true,"sort_order":null,"sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":63,"name":"comparison","color":"F1592A","text_color":"FFFFFF","slug":"comparison","topic_count":11,"post_count":137,"position":32,"description":"Topics comparing Discourse to other platforms.","description_text":"Topics comparing Discourse to other platforms.","description_excerpt":"Topics comparing Discourse to other platforms.","topic_url":"/t/about-the-comparison-category/65736","read_restricted":false,"permission":null,"parent_category_id":35,"notification_level":1,"topic_template":"### About PLATFORM_NAME\n\nA blurb about the platform being compared to Discourse. \n\nhttp://link.to.website\n\n ### Previous discussions:\n\nlinks to previous discussions related\n\n### Importer status\n\nIs there an official Discourse importer? Where is it? ","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":105,"name":"community support program","color":"92278F","text_color":"FFFFFF","slug":"support-program","topic_count":4,"post_count":27,"position":33,"description":"Get recognition for your work in helping and supporting Discourse communities by joining our Community Support Program.","description_text":"Get recognition for your work in helping and supporting Discourse communities by joining our Community Support Program.","description_excerpt":"Get recognition for your work in helping and supporting Discourse communities by joining our Community Support Program.","topic_url":"/t/about-the-community-support-program-category/193906","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":true,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":124,"name":"General","color":"25AAE2","text_color":"FFFFFF","slug":"general","topic_count":155,"post_count":1408,"position":35,"description":"Create topics here that don’t fit into any other existing category.","description_text":"Create topics here that don’t fit into any other existing category.","description_excerpt":"Create topics here that don’t fit into any other existing category.","topic_url":"/t/about-the-general-category/237517","read_restricted":false,"permission":null,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":17,"name":"Uncategorized","color":"AB9364","text_color":"FFFFFF","slug":"uncategorized","topic_count":0,"post_count":0,"position":77,"description":"Topics that don't need a category, or don't fit into any other existing category.","description_text":"Topics that don't need a category, or don't fit into any other existing category.","description_excerpt":"Topics that don't need a category, or don't fit into any other existing category.","topic_url":"/t/","read_restricted":false,"permission":null,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false}]}}} \ No newline at end of file +{"posts":[{"id":218354,"name":"Robin Ward","username":"eviltrout","avatar_template":"/user_avatar/meta.discourse.org/eviltrout/{size}/5275_2.png","created_at":"2016-08-24T20:48:02.587Z","like_count":15,"blurb":"Automated tests are a great way to protect your code against future regressions. Many people are familiar with how to do this in our Rails codebase with http://rspec.info/ rspec , but the Javascript s...","post_number":1,"topic_title_headline":"Write acceptance tests and component tests for Ember code in Discourse","topic_id":49167},{"id":138484,"name":"Robin Ward","username":"eviltrout","avatar_template":"/user_avatar/meta.discourse.org/eviltrout/{size}/5275_2.png","created_at":"2015-08-27T21:32:26.407Z","like_count":29,"blurb":"Previous tutorial: https://meta.discourse.org/t/developing-discourse-plugins-part-5-add-an-admin-interface/31761 Developing Discourse Plugins - Part 5 - Add an admin interface Did you know that Discou...","post_number":1,"topic_title_headline":"Developing Discourse Plugins - Part 6 - Add acceptance tests","topic_id":32619},{"id":1381521,"name":"Alan Tan","username":"tgxworld","avatar_template":"/user_avatar/meta.discourse.org/tgxworld/{size}/106117_2.png","created_at":"2023-10-24T23:13:37.118Z","like_count":16,"blurb":"Writing automated tests for themes is an important part of the theme development process which can help ensure that the features being introduced by a theme continues to work well overtime with core D...","post_number":1,"topic_title_headline":"End-to-end system testing for themes and theme components","topic_id":281579},{"id":311252,"name":"David Taylor","username":"david","avatar_template":"/user_avatar/meta.discourse.org/david/{size}/157490_2.png","created_at":"2017-07-26T14:09:58.126Z","like_count":17,"blurb":"Discourse has extensive frontend tests for core, plugins and themes. Once you have a functioning local development environment, those tests can be run locally in a number of different ways. Running te...","post_number":1,"topic_title_headline":"How to run Discourse core, plugin and theme QUnit test suites","topic_id":66857},{"id":59431,"name":"Erlend Sogge Heggen","username":"erlend_sh","avatar_template":"/user_avatar/meta.discourse.org/erlend_sh/{size}/119475_2.png","created_at":"2014-07-03T11:45:09.463Z","like_count":5,"blurb":"...view=1 to an URL, but the emulator has the added benefit of letting you select the screen profile of a specific device. I also tested all of the most popular online screen emulators, but unfortunately...","post_number":1,"topic_title_headline":"Test Discourse in mobile screen emulator","topic_id":17155},{"id":1419473,"name":"","username":"ToddZ","avatar_template":"/user_avatar/meta.discourse.org/toddz/{size}/328350_2.png","created_at":"2023-12-12T10:51:08.401Z","like_count":2,"blurb":"...amount of time troubleshooting inbound email because Discourse was rejecting every reply-by-email from my fake users. It had worked fine when I first tested several weeks ago… I finally realized that ...","post_number":1,"topic_title_headline":"Tip: when testing inbound email with fake user accounts…","topic_id":288363},{"id":722424,"name":"Falco","username":"Falco","avatar_template":"/user_avatar/meta.discourse.org/falco/{size}/179432_2.png","created_at":"2020-03-26T21:31:38.463Z","like_count":17,"blurb":"Continuing the discussion from https://meta.discourse.org/t/user-api-keys-specification/48536 User API keys specification : I created a small utility script in order to test User API keys locally. Fir...","post_number":1,"topic_title_headline":"Generate User API Keys for testing","topic_id":145744},{"id":266441,"name":"Andrew Waugh","username":"JagWaugh","avatar_template":"/user_avatar/meta.discourse.org/jagwaugh/{size}/69335_2.png","created_at":"2017-03-03T13:34:08.009Z","like_count":19,"blurb":"Regardless of if you're a moderator or an admin, you will no doubt at some time think about making some change to your live site and wonder if this will bring shame on you, and/or cause yourself an en...","post_number":1,"topic_title_headline":"Build a sandbox to test changes before making them live","topic_id":58298},{"id":582008,"name":"","username":"Wurzelseppi","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/w/eada6e/{size}.png","created_at":"2019-05-21T10:42:51.099Z","like_count":0,"blurb":"Hi guys, just wanted to migrate from 2.3.0beta9 to stable release and got this error: What can I do here ? Caused by: PG::UndefinedColumn: ERROR: column \"email_private_messages\" of relation \"user_opti...","post_number":1,"topic_title_headline":"Migrate from tests-passed to stable","topic_id":118296},{"id":1421414,"name":"Robert","username":"merefield","avatar_template":"/user_avatar/meta.discourse.org/merefield/{size}/176214_2.png","created_at":"2023-12-14T17:11:26.575Z","like_count":1,"blurb":"Restarting the server, restarting the container, console rebuilding doesn't help. Container is up as I can ./launcher enter app Got a bunch of these in logs, ideas on how to investigate redis failure?...","post_number":1,"topic_title_headline":"502 Bad Gateway after online rebuild of tests-passed Production just now","topic_id":288705},{"id":1204506,"name":"Coin-coin le Canapin","username":"Canapin","avatar_template":"/user_avatar/meta.discourse.org/canapin/{size}/119591_2.png","created_at":"2022-12-02T20:49:55.867Z","like_count":1,"blurb":"Hi! I want to add support of /shorts/ Youtube link. My modification of the YoutubeOnebox class works, but it is required that I add a test in https://github.com/discourse/discourse/blob/493d437e79f88a...","post_number":1,"topic_title_headline":"Trouble on adding a simple unit test for Youtube oneboxing","topic_id":247546},{"id":1339808,"name":"Robert","username":"merefield","avatar_template":"/user_avatar/meta.discourse.org/merefield/{size}/176214_2.png","created_at":"2023-08-07T10:05:22.444Z","like_count":2,"blurb":"I have a strange issue with QUnit. This test is extremely simple and should be straightforward … but … A plugin setting is changing from those I have set up. https://github.com/paviliondev/discourse-l...","post_number":1,"topic_title_headline":"Strange QUnit behaviour?: test failing because setting value doesn’t survive","topic_id":274165},{"id":1211823,"name":"Robert","username":"merefield","avatar_template":"/user_avatar/meta.discourse.org/merefield/{size}/176214_2.png","created_at":"2022-12-16T18:32:29.231Z","like_count":1,"blurb":"...presenting formatted location on User Card by merefield · Pull Request #73 · paviliondev/discourse-locations · GitHub I'm attempting to cover the change with a new Front End test. But the test fails t...","post_number":1,"topic_title_headline":"Is it possible to override the Site object with own fixture during Front End tests of a Plugin?","topic_id":249167},{"id":1408389,"name":"Pierre Romera","username":"pirhoo","avatar_template":"/user_avatar/meta.discourse.org/pirhoo/{size}/120058_2.png","created_at":"2023-11-22T18:46:46.469Z","like_count":5,"blurb":"...m setting up a new plugin based on the https://github.com/discourse/discourse-plugin-skeleton/tree/main/assets skeleton you provided which already helped me a lot. I am now writing tests, both for the...","post_number":1,"topic_title_headline":"Acceptance tests failing on Github Actions","topic_id":286355},{"id":443129,"name":"JK Baseer","username":"JKBaseer","avatar_template":"/user_avatar/meta.discourse.org/jkbaseer/{size}/80471_2.png","created_at":"2018-07-01T17:12:48.446Z","like_count":0,"blurb":"...but still could myself. Background: I installed discourse using digitalocean oneclick installer. The website is running under http://forum.example.org forum.example.org without any problem except the ...","post_number":1,"topic_title_headline":"There was a problem sending the test email","topic_id":91312},{"id":1412219,"name":"Robert","username":"merefield","avatar_template":"/user_avatar/meta.discourse.org/merefield/{size}/176214_2.png","created_at":"2023-11-29T23:18:11.815Z","like_count":1,"blurb":"I'm trying to create a foreign key relationship with the Topics table. The problem is it is failing in github workflow test environment during tests for the strangest reason, it is trying to access a ...","post_number":1,"topic_title_headline":"Strange migration error in tests during GH workflow","topic_id":287022},{"id":1160803,"name":"Bryan Joseph","username":"Bryan_Joseph","avatar_template":"/user_avatar/meta.discourse.org/bryan_joseph/{size}/273248_2.png","created_at":"2022-09-07T20:22:11.191Z","like_count":1,"blurb":"...SETTINGS ==================== DISCOURSE_HOSTNAME=url SMTP_ADDRESS=smtp.mailgun.org DEVELOPER_EMAILS=REDACTED SMTP_PASSWORD=REDACTED SMTP_PORT=2525 SMTP_USER_NAME=url LETSENCRYPT_ACCOUNT_EMAIL=REDACTED...","post_number":1,"topic_title_headline":"Smtp doctor test using port 465 even though its configured to use 2525","topic_id":238372},{"id":725228,"name":"james.network","username":"sunjam","avatar_template":"/user_avatar/meta.discourse.org/sunjam/{size}/175682_2.png","created_at":"2020-03-31T18:51:44.216Z","like_count":0,"blurb":"...Redis or updating it; it hasn't really been touched in the last 8+ months. I have not agentlly dealt with Redis before, but our Tests-Pass Discourse instance was setup using https://hub.docker.com/r...","post_number":1,"topic_title_headline":"Sidekiq not running. Sidekiq heartbeat test failed, restarting","topic_id":146326},{"id":1240758,"name":"Jay Pfaffman","username":"pfaffman","avatar_template":"/user_avatar/meta.discourse.org/pfaffman/{size}/120154_2.png","created_at":"2023-02-16T22:12:10.456Z","like_count":4,"blurb":"I see that the discourse-plugin-skeleton now has this: uses: discourse/.github/.github/workflows/discourse-plugin.yml@v1 so we don't have to keep updating stuff. But I have a plugin that requires the ...","post_number":1,"topic_title_headline":"Tests for plugin that requires a plugin","topic_id":255406},{"id":1197679,"name":"","username":"SilK","avatar_template":"/user_avatar/meta.discourse.org/silk/{size}/268124_2.png","created_at":"2022-11-18T17:03:46.056Z","like_count":0,"blurb":"...a new dev environment for working on plugins. Discourse is up to date with the main branch. I need to restart Ember in order to test changes made to the front end. This includes changes to Handlebars,...","post_number":1,"topic_title_headline":"Need to restart Ember in order to test front-end changes","topic_id":246069},{"id":1103913,"name":"Banibrata Dutta","username":"bdutta","avatar_template":"/user_avatar/meta.discourse.org/bdutta/{size}/259973_2.png","created_at":"2022-05-15T18:02:33.290Z","like_count":0,"blurb":"...only in a captive host-only testbed, so wondering if there is any local network SMTP daemon / service that I could start to complete the testing ? I'm happy with 100% command line mail client and serv...","post_number":1,"topic_title_headline":"Bitnami Discourse VM on Virtualbox + SMTP mail-server for testing","topic_id":227090},{"id":722608,"name":"Lona Lee","username":"Lona_Lee","avatar_template":"/user_avatar/meta.discourse.org/lona_lee/{size}/169072_2.png","created_at":"2020-03-27T07:14:10.239Z","like_count":1,"blurb":"Hello. I'm trying to get email setup working on my discourse instance. Done set-up properly and looks fine(no errors), so sent test emails. (logs confirmed : \"Admin\" - \"Emails\" - \"Sent\") However, I ha...","post_number":1,"topic_title_headline":"Test emails sent but","topic_id":145781},{"id":679950,"name":"Oleg Bovykin","username":"arrowcircle","avatar_template":"/user_avatar/meta.discourse.org/arrowcircle/{size}/100035_2.png","created_at":"2020-01-01T11:20:33.618Z","like_count":1,"blurb":"Hi! I found strange error in my admin page, that sidekiq is not running. I opened logs and found hundreds errors like: /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/logster-2.5.1/lib/logster/logger...","post_number":1,"topic_title_headline":"Sidekiq heartbeat test failed, restarting","topic_id":137496},{"id":1033333,"name":"М. М.","username":"М_М","avatar_template":"/user_avatar/meta.discourse.org/м_м/{size}/243710_2.png","created_at":"2021-12-20T13:52:10.488Z","like_count":0,"blurb":"...user, the logs say like this Job exception: could not get 3xx (421: 421 Domain sandbox410fe5c7bb85483c941c05b4ec5f3495.mailgun.org is not allowed to send: Sandbox subdomains are for test purposes only...","post_number":1,"topic_title_headline":"MailGun & Discourse: Sandbox subdomains are for test purposes only. Please add your own domain","topic_id":212684},{"id":498559,"name":"","username":"desrocchi","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/d/eb9ed0/{size}.png","created_at":"2018-11-14T15:20:59.607Z","like_count":0,"blurb":"Is there a way for me to see or test the admin options in the demo area? I am just a moderator on the platform we use but I would like to see which options could be of use without having to install th...","post_number":1,"topic_title_headline":"Test admin features without having to install Discourse","topic_id":102035},{"id":596557,"name":"Flaviu","username":"UnivacTwo","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/u/df705f/{size}.png","created_at":"2019-06-25T17:12:34.710Z","like_count":0,"blurb":"Let's encrypt has a limit of how many certificates can be generated in a week for the same domain. Unfortunately we reach this limit and we cannot generate a new certificate this week. We did a backup...","post_number":1,"topic_title_headline":"Install discourse with a staging (test) ssl certificate","topic_id":121299},{"id":583541,"name":"mark78","username":"Mark_Schmucker","avatar_template":"/user_avatar/meta.discourse.org/mark_schmucker/{size}/124810_2.png","created_at":"2019-05-24T00:14:35.895Z","like_count":0,"blurb":"Should I be able to run any Badge Query in https://meta.discourse.org/t/32566 Data Explorer ? I want to create a custom Badge Query using \"Appreciated\" as a starting point. I type the Appreciated quer...","post_number":1,"topic_title_headline":"Problem testing Badge Query from Data Explorer","topic_id":118568},{"id":569209,"name":"Penar Musaraj","username":"pmusaraj","avatar_template":"/user_avatar/meta.discourse.org/pmusaraj/{size}/119489_2.png","created_at":"2019-04-25T01:24:56.741Z","like_count":26,"blurb":"...device and installing the app via TestFlight: https://testflight.apple.com/join/NkdBQgmg testflight.apple.com https://testflight.apple.com/join/NkdBQgmg TestFlight - Apple Using TestFlight is a great ...","post_number":1,"topic_title_headline":"New iOS mobile app beta available for testing","topic_id":115912},{"id":1090520,"name":"Mac玩儿法","username":"waerfa","avatar_template":"/user_avatar/meta.discourse.org/waerfa/{size}/216044_2.png","created_at":"2022-04-17T21:46:04.755Z","like_count":0,"blurb":"...rebuild the container: git pull ./launcher rebuild app I got the fatal error which shows: FAILED -------------------- Pups::ExecError: cd /var/www/discourse & & git fetch --depth 1 origin tests-passed...","post_number":1,"topic_title_headline":"502 Bad Gateway after trying to rebuild test-passed branch","topic_id":224560},{"id":860593,"name":"james.network","username":"sunjam","avatar_template":"/user_avatar/meta.discourse.org/sunjam/{size}/175682_2.png","created_at":"2020-12-11T18:44:18.021Z","like_count":1,"blurb":"Continuing the discussion from https://meta.discourse.org/t/postgresql-13-update/172563/27 PostgreSQL 13 update : Run into trouble while updating 2.7.0beta1 Tests-Pass in order to remove some troubles...","post_number":1,"topic_title_headline":"Forum offline due to failed rebuilds on Tests-Pass","topic_id":173019},{"id":960683,"name":"","username":"daniyal","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/d/58f4c7/{size}.png","created_at":"2021-07-08T19:58:55.634Z","like_count":3,"blurb":"...which we want to experiment. An example would be to experiment different styles of topic list view. For this we are using Google Optimize A/B testing. Currently we plan to show theme without changes t...","post_number":1,"topic_title_headline":"[A/B Testing] Changing parent CSS class based on experiment variable","topic_id":196501},{"id":1389188,"name":"Angus McLeod","username":"angus","avatar_template":"/user_avatar/meta.discourse.org/angus/{size}/341715_2.png","created_at":"2023-10-20T03:44:49.737Z","like_count":7,"blurb":"I've been looking at the performance of the https://meta.discourse.org/t/activitypub-plugin/266794 ActivityPub plugin recently and considering the best ways to reliably test, and prove, performance fo...","post_number":1,"topic_title_headline":"Code-level performance testing","topic_id":282856},{"id":1329017,"name":"","username":"dodibi","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/d/9fc348/{size}.png","created_at":"2023-07-19T12:45:57.446Z","like_count":0,"blurb":"Hello everyone! I'm currently facing some challenges while configuring my local environment to run discourse tests in a docker container. My main objective is to run the core tests with plugins attach...","post_number":1,"topic_title_headline":"Running core tests in docker environment","topic_id":272112},{"id":421687,"name":"Jay Pfaffman","username":"pfaffman","avatar_template":"/user_avatar/meta.discourse.org/pfaffman/{size}/120154_2.png","created_at":"2018-05-11T22:03:23.232Z","like_count":0,"blurb":"Is there a way to initiate an email test from the Rails console? For a zillion reasons I would love to be able to send a test email without having to create an account. I've looked in config/routes.rb...","post_number":1,"topic_title_headline":"Email test from the console?","topic_id":87295},{"id":272436,"name":"David Taylor","username":"david","avatar_template":"/user_avatar/meta.discourse.org/david/{size}/157490_2.png","created_at":"2017-03-20T20:29:06.608Z","like_count":1,"blurb":"It is my understanding that running rake qunit:test should run all of the qunit tests in Discourse, including those for any installed plugins. However, when I run the task in the docker development en...","post_number":1,"topic_title_headline":"Plugin QUnit tests are not running as part of rake qunit:test","topic_id":59577},{"id":899205,"name":"","username":"JQ331","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/j/41988e/{size}.png","created_at":"2021-03-03T18:09:29.452Z","like_count":6,"blurb":"I came across this https://blog.codinghorror.com/low-fi-usability-testing/ excellent article on how to do low-fi usability testing by @codinghorror . Usability testing (and user testing in general) is...","post_number":1,"topic_title_headline":"How does the Discourse team do usability testing?","topic_id":181856},{"id":530438,"name":"","username":"kleinfreund","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/k/a6a055/{size}.png","created_at":"2019-02-01T09:52:27.150Z","like_count":1,"blurb":"One can write tests for the backend of a plugin. For example, I created the following file in my plugin directory: spec/lib/route_store_spec.rb : require 'rails_helper' describe MyPlugin::RouteStore d...","post_number":1,"topic_title_headline":"Advice on writing Ruby tests for plugins","topic_id":108110},{"id":260691,"name":"Rimian Perkins","username":"rimian","avatar_template":"/user_avatar/meta.discourse.org/rimian/{size}/120658_2.png","created_at":"2017-02-13T02:48:10.177Z","like_count":0,"blurb":"What's the best way to (QUnit) assert an element on the page has some content in it? This passes: ok($.trim($('.foo').text()) == 'bar', 'content bar renders on page'); But isn't very practical. Is the...","post_number":1,"topic_title_headline":"Acceptance test content is present on page","topic_id":57292},{"id":1432567,"name":"Ayke","username":"rrit","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/r/b5ac83/{size}.png","created_at":"2024-01-09T16:53:57.916Z","like_count":0,"blurb":"Right now Discourse on meta.discourse.org serves the mobile-view instead of the crawler-view to the https://search.google.com/test/rich-results Google Rich Results Test . As there is no Schema Markup ...","post_number":1,"topic_title_headline":"Google Search Console/Schema Markup test tool “Google Rich Results Test”: mobile-view instead of crawler-view","topic_id":291039},{"id":1305996,"name":"Larry Diehl","username":"larrytheliquid","avatar_template":"/user_avatar/meta.discourse.org/larrytheliquid/{size}/310576_2.png","created_at":"2023-06-08T22:35:51.914Z","like_count":1,"blurb":"Hi Discourse Community! :slight_smile: I've been working on tech ( https://colimit.io Colimit ) that helps people apply https://en.wikipedia.org/wiki/Model-based_testing Model-based testing to test th...","post_number":1,"topic_title_headline":"Experiments with Model-Based Testing","topic_id":267737},{"id":1135474,"name":"Robert","username":"merefield","avatar_template":"/user_avatar/meta.discourse.org/merefield/{size}/176214_2.png","created_at":"2022-07-20T08:48:28.868Z","like_count":0,"blurb":"...my case running: rake \"plugin:qunit[discourse-multilingual]\" with a branch installed. I'm declaring a function in my initializer (i'm extending I18n ) The tests sometimes (25%?) seem to run before the...","post_number":1,"topic_title_headline":"Qunit tests not deterministic in Plugin?","topic_id":233389},{"id":672801,"name":"Jay Pfaffman","username":"pfaffman","avatar_template":"/user_avatar/meta.discourse.org/pfaffman/{size}/120154_2.png","created_at":"2019-12-12T21:01:31.303Z","like_count":0,"blurb":"Thanks, @Mittineague ! After a while, that made sense. I even wrote a spec, but even before I added my spec (and when I reverted to before I added any code), specs fail because: An error occurred whil...","post_number":1,"topic_title_headline":"Issues migrating test database","topic_id":135876},{"id":871105,"name":"","username":"Alteras","avatar_template":"/user_avatar/meta.discourse.org/alteras/{size}/179824_2.png","created_at":"2021-01-07T19:56:05.071Z","like_count":4,"blurb":"Hello! I'm currently working on a Markdown Extension/plugin that adds quite a number of BBCode tags, and I am looking to write QUnit Acceptance tests for them (I got really tired of constantly checkin...","post_number":1,"topic_title_headline":"Acceptance Test for Markdown Extension?","topic_id":175413},{"id":747613,"name":"","username":"xrav3nz","avatar_template":"/user_avatar/meta.discourse.org/xrav3nz/{size}/76894_2.png","created_at":"2020-05-08T04:27:56.920Z","like_count":8,"blurb":"...development - #2 by taylorthurlow - A May Of WTFs - Ruby on Rails Discussions Not sure if we have explored this before, but Rails can automatically maintain test databse schema with ActiveRecord::Migr...","post_number":1,"topic_title_headline":"Auto migrate test database schema","topic_id":150786},{"id":583961,"name":"Kim Miller","username":"kimardenmiller","avatar_template":"/user_avatar/meta.discourse.org/kimardenmiller/{size}/119631_2.png","created_at":"2019-05-24T22:21:24.996Z","like_count":3,"blurb":"Adding some polls API endpoints for PR to discourse_api, which work fine. Now I'm trying to understand how to create tests before submitting the PR, e.g.: require 'spec_helper' describe DiscourseApi::...","post_number":1,"topic_title_headline":"Building Tests for New discourse_api Endpoints","topic_id":118639},{"id":621707,"name":"Andrew Lank","username":"alank","avatar_template":"https://avatars.discourse-cdn.com/v4/letter/a/c89c15/{size}.png","created_at":"2019-08-17T02:13:06.679Z","like_count":1,"blurb":"In my development and testing I'm running a Discourse instance (Docker Discourse from Bitnami) and it fulfills most of my API testing for our API service which talks to Discourse, however I now need t...","post_number":1,"topic_title_headline":"Seed or API calls to create test users","topic_id":126025},{"id":334186,"name":"Chris","username":"ChrisBeach","avatar_template":"/user_avatar/meta.discourse.org/chrisbeach/{size}/214628_2.png","created_at":"2017-10-01T08:18:48.148Z","like_count":1,"blurb":"...from the core team. I propose that on hitting the upgrade button, a new docker image is built in the background, and within it, acceptance tests of all plugins are run before the switch-over happens f...","post_number":1,"topic_title_headline":"Smoke-testing plugins during upgrade process","topic_id":71118},{"id":288991,"name":"David Taylor","username":"david","avatar_template":"/user_avatar/meta.discourse.org/david/{size}/157490_2.png","created_at":"2017-05-16T10:28:58.496Z","like_count":1,"blurb":"I'm trying to use the docker image for tests both on my mac, and also https://meta.discourse.org/t/setting-up-plugin-continuous-integration-tests-on-travis-ci/59612 on travis . For a while now the qun...","post_number":1,"topic_title_headline":"QUnit tests won’t pass in discourse_dev docker image","topic_id":62797},{"id":187533,"name":"Sckott","username":"sckott","avatar_template":"/user_avatar/meta.discourse.org/sckott/{size}/115359_2.png","created_at":"2016-04-21T19:15:00.191Z","like_count":0,"blurb":"What's the best or fastest way to get Discourse installed on Travis for testing a client for the Discourse API ? It appears as though the discourse_api gem uses webmock so I think does not use a real ...","post_number":1,"topic_title_headline":"Testing a Discourse API client on Travis-CI","topic_id":42947},{"id":966756,"name":"Connor Parrish","username":"Connor_Parrish","avatar_template":"/user_avatar/meta.discourse.org/connor_parrish/{size}/225463_2.png","created_at":"2021-07-22T16:57:05.885Z","like_count":1,"blurb":"When you're conditionally adding a PostMenuButton using the plugin-api , the extra button is included in _extraButtons in between acceptance tests. When I run tests, if the tests where the button shou...","post_number":1,"topic_title_headline":"PostMenu’s ‘_extraButtons’ isn’t reset in between acceptance tests","topic_id":197887}],"topics":[{"id":49167,"title":"Write acceptance tests and component tests for Ember code in Discourse","fancy_title":"Write acceptance tests and component tests for Ember code in Discourse","slug":"write-acceptance-tests-and-component-tests-for-ember-code-in-discourse","posts_count":3,"reply_count":1,"highest_post_number":3,"created_at":"2016-08-24T20:48:02.492Z","last_posted_at":"2017-02-01T18:22:01.859Z","bumped":true,"bumped_at":"2017-02-01T18:22:01.859Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["tutorial","ember","testing"],"tags_descriptions":{},"category_id":56,"has_accepted_answer":false},{"id":32619,"title":"Developing Discourse Plugins - Part 6 - Add acceptance tests","fancy_title":"Developing Discourse Plugins - Part 6 - Add acceptance tests","slug":"developing-discourse-plugins-part-6-add-acceptance-tests","posts_count":33,"reply_count":26,"highest_post_number":38,"created_at":"2015-08-27T21:32:26.323Z","last_posted_at":"2022-06-02T11:06:38.274Z","bumped":true,"bumped_at":"2022-06-02T11:06:38.274Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["plugins","tutorial","plugin-guides","testing"],"tags_descriptions":{"plugins":""},"category_id":56,"has_accepted_answer":false},{"id":281579,"title":"End-to-end system testing for themes and theme components","fancy_title":"End-to-end system testing for themes and theme components","slug":"end-to-end-system-testing-for-themes-and-theme-components","posts_count":1,"reply_count":0,"highest_post_number":1,"created_at":"2023-10-24T23:13:37.118Z","last_posted_at":"2023-10-24T23:13:37.118Z","bumped":true,"bumped_at":"2023-11-13T23:20:01.596Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["how-to","themes"],"tags_descriptions":{"how-to":"How to guides contain steps to follow to solve a specific problem"},"category_id":56,"has_accepted_answer":false},{"id":66857,"title":"How to run Discourse core, plugin and theme QUnit test suites","fancy_title":"How to run Discourse core, plugin and theme QUnit test suites","slug":"how-to-run-discourse-core-plugin-and-theme-qunit-test-suites","posts_count":1,"reply_count":2,"highest_post_number":1,"created_at":"2017-07-26T14:09:58.032Z","last_posted_at":"2017-07-26T14:09:58.126Z","bumped":true,"bumped_at":"2023-09-04T17:56:33.079Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["how-to"],"tags_descriptions":{"how-to":"How to guides contain steps to follow to solve a specific problem"},"category_id":56,"has_accepted_answer":false},{"id":17155,"title":"Test Discourse in mobile screen emulator","fancy_title":"Test Discourse in mobile screen emulator","slug":"test-discourse-in-mobile-screen-emulator","posts_count":3,"reply_count":1,"highest_post_number":3,"created_at":"2014-07-03T11:45:09.360Z","last_posted_at":"2014-10-12T22:19:24.137Z","bumped":true,"bumped_at":"2014-10-12T22:19:24.137Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["how-to"],"tags_descriptions":{"how-to":"How to guides contain steps to follow to solve a specific problem"},"category_id":56,"has_accepted_answer":false},{"id":288363,"title":"Tip: when testing inbound email with fake user accounts...","fancy_title":"Tip: when testing inbound email with fake user accounts…","slug":"tip-when-testing-inbound-email-with-fake-user-accounts","posts_count":1,"reply_count":0,"highest_post_number":1,"created_at":"2023-12-12T10:51:07.868Z","last_posted_at":"2023-12-12T10:51:08.401Z","bumped":true,"bumped_at":"2023-12-12T10:51:08.401Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["email"],"tags_descriptions":{},"category_id":55,"has_accepted_answer":false},{"id":145744,"title":"Generate User API Keys for testing","fancy_title":"Generate User API Keys for testing","slug":"generate-user-api-keys-for-testing","posts_count":4,"reply_count":1,"highest_post_number":4,"created_at":"2020-03-26T21:31:38.269Z","last_posted_at":"2022-08-07T15:27:18.788Z","bumped":true,"bumped_at":"2022-08-07T15:27:18.788Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["how-to"],"tags_descriptions":{"how-to":"How to guides contain steps to follow to solve a specific problem"},"category_id":56,"has_accepted_answer":false},{"id":58298,"title":"Build a sandbox to test changes before making them live","fancy_title":"Build a sandbox to test changes before making them live","slug":"build-a-sandbox-to-test-changes-before-making-them-live","posts_count":21,"reply_count":13,"highest_post_number":21,"created_at":"2017-03-03T13:34:07.921Z","last_posted_at":"2022-02-16T23:18:54.680Z","bumped":true,"bumped_at":"2022-02-16T23:18:54.680Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["how-to"],"tags_descriptions":{"how-to":"How to guides contain steps to follow to solve a specific problem"},"category_id":55,"has_accepted_answer":false},{"id":118296,"title":"Migrate from tests-passed to stable","fancy_title":"Migrate from tests-passed to stable","slug":"migrate-from-tests-passed-to-stable","posts_count":12,"reply_count":7,"highest_post_number":12,"created_at":"2019-05-21T10:42:51.017Z","last_posted_at":"2019-06-21T15:55:53.072Z","bumped":true,"bumped_at":"2019-05-22T15:55:47.651Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":288705,"title":"502 Bad Gateway after online rebuild of tests-passed Production just now","fancy_title":"502 Bad Gateway after online rebuild of tests-passed Production just now","slug":"502-bad-gateway-after-online-rebuild-of-tests-passed-production-just-now","posts_count":6,"reply_count":1,"highest_post_number":6,"created_at":"2023-12-14T17:11:26.387Z","last_posted_at":"2024-01-13T17:37:27.535Z","bumped":true,"bumped_at":"2023-12-14T17:36:57.066Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":247546,"title":"Trouble on adding a simple unit test for Youtube oneboxing","fancy_title":"Trouble on adding a simple unit test for Youtube oneboxing","slug":"trouble-on-adding-a-simple-unit-test-for-youtube-oneboxing","posts_count":10,"reply_count":5,"highest_post_number":10,"created_at":"2022-12-02T20:49:55.713Z","last_posted_at":"2023-01-05T17:47:40.629Z","bumped":true,"bumped_at":"2022-12-06T17:47:28.771Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":["onebox","testing"],"tags_descriptions":{},"category_id":7,"has_accepted_answer":true},{"id":274165,"title":"Strange QUnit behaviour?: test failing because setting value doesn't survive","fancy_title":"Strange QUnit behaviour?: test failing because setting value doesn’t survive","slug":"strange-qunit-behaviour-test-failing-because-setting-value-doesnt-survive","posts_count":5,"reply_count":2,"highest_post_number":5,"created_at":"2023-08-07T10:05:22.298Z","last_posted_at":"2023-09-06T11:19:24.509Z","bumped":true,"bumped_at":"2023-08-07T11:24:27.150Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":true},{"id":249167,"title":"Is it possible to override the Site object with own fixture during Front End tests of a Plugin?","fancy_title":"Is it possible to override the Site object with own fixture during Front End tests of a Plugin?","slug":"is-it-possible-to-override-the-site-object-with-own-fixture-during-front-end-tests-of-a-plugin","posts_count":4,"reply_count":0,"highest_post_number":4,"created_at":"2022-12-16T18:32:29.079Z","last_posted_at":"2022-12-28T21:57:56.070Z","bumped":true,"bumped_at":"2022-12-28T21:57:56.070Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":true},{"id":286355,"title":"Acceptance tests failing on Github Actions","fancy_title":"Acceptance tests failing on Github Actions","slug":"acceptance-tests-failing-on-github-actions","posts_count":6,"reply_count":0,"highest_post_number":6,"created_at":"2023-11-22T18:46:46.128Z","last_posted_at":"2023-11-24T13:59:40.100Z","bumped":true,"bumped_at":"2023-11-24T13:59:40.100Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":true},{"id":91312,"title":"There was a problem sending the test email","fancy_title":"There was a problem sending the test email","slug":"there-was-a-problem-sending-the-test-email","posts_count":8,"reply_count":5,"highest_post_number":8,"created_at":"2018-07-01T17:12:48.366Z","last_posted_at":"2018-08-01T09:28:21.935Z","bumped":true,"bumped_at":"2018-07-02T09:28:16.014Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":287022,"title":"Strange migration error in tests during GH workflow","fancy_title":"Strange migration error in tests during GH workflow","slug":"strange-migration-error-in-tests-during-gh-workflow","posts_count":6,"reply_count":3,"highest_post_number":6,"created_at":"2023-11-29T23:18:11.686Z","last_posted_at":"2023-12-31T15:09:05.633Z","bumped":true,"bumped_at":"2023-12-01T15:08:31.257Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":true},{"id":238372,"title":"Smtp doctor test using port 465 even though its configured to use 2525","fancy_title":"Smtp doctor test using port 465 even though its configured to use 2525","slug":"smtp-doctor-test-using-port-465-even-though-its-configured-to-use-2525","posts_count":12,"reply_count":7,"highest_post_number":12,"created_at":"2022-09-07T20:22:10.976Z","last_posted_at":"2022-10-09T00:21:55.656Z","bumped":true,"bumped_at":"2022-09-09T00:21:48.272Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":146326,"title":"Sidekiq not running. Sidekiq heartbeat test failed, restarting","fancy_title":"Sidekiq not running. Sidekiq heartbeat test failed, restarting","slug":"sidekiq-not-running-sidekiq-heartbeat-test-failed-restarting","posts_count":16,"reply_count":9,"highest_post_number":16,"created_at":"2020-03-31T18:51:44.061Z","last_posted_at":"2020-06-10T01:39:28.366Z","bumped":true,"bumped_at":"2020-05-11T01:39:26.054Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":["unsupported-install"],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":255406,"title":"Tests for plugin that requires a plugin","fancy_title":"Tests for plugin that requires a plugin","slug":"tests-for-plugin-that-requires-a-plugin","posts_count":3,"reply_count":0,"highest_post_number":3,"created_at":"2023-02-16T22:12:10.344Z","last_posted_at":"2023-02-28T16:10:36.498Z","bumped":true,"bumped_at":"2023-02-28T20:21:06.122Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":true},{"id":246069,"title":"Need to restart Ember in order to test front-end changes","fancy_title":"Need to restart Ember in order to test front-end changes","slug":"need-to-restart-ember-in-order-to-test-front-end-changes","posts_count":5,"reply_count":1,"highest_post_number":5,"created_at":"2022-11-18T17:03:45.900Z","last_posted_at":"2022-11-18T18:17:53.648Z","bumped":true,"bumped_at":"2022-11-18T18:17:53.648Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":true},{"id":227090,"title":"Bitnami Discourse VM on Virtualbox + SMTP mail-server for testing","fancy_title":"Bitnami Discourse VM on Virtualbox + SMTP mail-server for testing","slug":"bitnami-discourse-vm-on-virtualbox-smtp-mail-server-for-testing","posts_count":3,"reply_count":0,"highest_post_number":3,"created_at":"2022-05-15T18:02:33.136Z","last_posted_at":"2022-05-16T09:10:43.794Z","bumped":true,"bumped_at":"2022-05-16T09:10:43.794Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":["unsupported-install"],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":145781,"title":"Test emails sent but","fancy_title":"Test emails sent but","slug":"test-emails-sent-but","posts_count":5,"reply_count":2,"highest_post_number":5,"created_at":"2020-03-27T07:14:10.116Z","last_posted_at":"2020-04-29T02:52:31.927Z","bumped":true,"bumped_at":"2020-03-30T02:52:29.062Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":137496,"title":"Sidekiq heartbeat test failed, restarting","fancy_title":"Sidekiq heartbeat test failed, restarting","slug":"sidekiq-heartbeat-test-failed-restarting","posts_count":13,"reply_count":8,"highest_post_number":13,"created_at":"2020-01-01T11:20:33.492Z","last_posted_at":"2020-02-11T23:09:42.375Z","bumped":true,"bumped_at":"2020-01-12T23:09:39.730Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":212684,"title":"MailGun & Discourse: Sandbox subdomains are for test purposes only. Please add your own domain","fancy_title":"MailGun & Discourse: Sandbox subdomains are for test purposes only. Please add your own domain","slug":"mailgun-discourse-sandbox-subdomains-are-for-test-purposes-only-please-add-your-own-domain","posts_count":4,"reply_count":2,"highest_post_number":5,"created_at":"2021-12-20T13:52:10.405Z","last_posted_at":"2022-01-19T15:27:11.368Z","bumped":true,"bumped_at":"2021-12-20T15:26:24.911Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":102035,"title":"Test admin features without having to install Discourse","fancy_title":"Test admin features without having to install Discourse","slug":"test-admin-features-without-having-to-install-discourse","posts_count":6,"reply_count":2,"highest_post_number":6,"created_at":"2018-11-14T15:20:59.484Z","last_posted_at":"2021-09-09T07:35:18.270Z","bumped":true,"bumped_at":"2021-09-09T07:35:18.270Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":121299,"title":"Install discourse with a staging (test) ssl certificate","fancy_title":"Install discourse with a staging (test) ssl certificate","slug":"install-discourse-with-a-staging-test-ssl-certificate","posts_count":5,"reply_count":1,"highest_post_number":5,"created_at":"2019-06-25T17:12:34.552Z","last_posted_at":"2023-04-01T03:25:32.925Z","bumped":true,"bumped_at":"2019-09-04T15:15:31.153Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":true},{"id":118568,"title":"Problem testing Badge Query from Data Explorer","fancy_title":"Problem testing Badge Query from Data Explorer","slug":"problem-testing-badge-query-from-data-explorer","posts_count":5,"reply_count":1,"highest_post_number":6,"created_at":"2019-05-24T00:14:35.814Z","last_posted_at":"2019-05-24T00:46:42.560Z","bumped":true,"bumped_at":"2019-05-24T00:46:42.560Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"bookmarked":null,"liked":null,"tags":["sql-triggered-badge"],"tags_descriptions":{"sql-triggered-badge":"SQL queries for custom triggered badges"},"category_id":148,"has_accepted_answer":true},{"id":115912,"title":"New iOS mobile app beta available for testing","fancy_title":"New iOS mobile app beta available for testing","slug":"new-ios-mobile-app-beta-available-for-testing","posts_count":49,"reply_count":31,"highest_post_number":49,"created_at":"2019-04-25T01:24:56.608Z","last_posted_at":"2019-05-31T17:31:02.516Z","bumped":true,"bumped_at":"2020-01-21T17:07:37.817Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":224560,"title":"502 Bad Gateway after trying to rebuild test-passed branch","fancy_title":"502 Bad Gateway after trying to rebuild test-passed branch","slug":"502-bad-gateway-after-trying-to-rebuild-test-passed-branch","posts_count":6,"reply_count":1,"highest_post_number":6,"created_at":"2022-04-17T21:46:04.598Z","last_posted_at":"2022-04-17T22:15:37.255Z","bumped":true,"bumped_at":"2022-04-17T22:15:37.255Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":false},{"id":173019,"title":"Forum offline due to failed rebuilds on Tests-Pass","fancy_title":"Forum offline due to failed rebuilds on Tests-Pass","slug":"forum-offline-due-to-failed-rebuilds-on-tests-pass","posts_count":2,"reply_count":0,"highest_post_number":2,"created_at":"2020-12-11T18:44:17.952Z","last_posted_at":"2020-12-11T19:12:11.141Z","bumped":true,"bumped_at":"2020-12-11T19:12:11.141Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":31,"has_accepted_answer":false},{"id":196501,"title":"[A/B Testing] Changing parent CSS class based on experiment variable","fancy_title":"[A/B Testing] Changing parent CSS class based on experiment variable","slug":"a-b-testing-changing-parent-css-class-based-on-experiment-variable","posts_count":3,"reply_count":1,"highest_post_number":3,"created_at":"2021-07-08T19:58:55.503Z","last_posted_at":"2021-07-15T18:19:21.092Z","bumped":true,"bumped_at":"2021-07-15T18:19:21.092Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":282856,"title":"Code-level performance testing","fancy_title":"Code-level performance testing","slug":"code-level-performance-testing","posts_count":2,"reply_count":0,"highest_post_number":2,"created_at":"2023-10-20T03:44:49.568Z","last_posted_at":"2023-10-23T23:29:59.741Z","bumped":true,"bumped_at":"2023-10-23T23:29:59.741Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":272112,"title":"Running core tests in docker environment","fancy_title":"Running core tests in docker environment","slug":"running-core-tests-in-docker-environment","posts_count":1,"reply_count":0,"highest_post_number":1,"created_at":"2023-07-19T12:45:57.266Z","last_posted_at":"2023-07-19T12:45:57.446Z","bumped":true,"bumped_at":"2023-07-19T12:45:57.446Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["docker","spec","testing"],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":87295,"title":"Email test from the console?","fancy_title":"Email test from the console?","slug":"email-test-from-the-console","posts_count":4,"reply_count":1,"highest_post_number":4,"created_at":"2018-05-11T22:03:23.101Z","last_posted_at":"2018-05-12T00:55:41.843Z","bumped":true,"bumped_at":"2018-05-12T00:55:41.843Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":59577,"title":"Plugin QUnit tests are not running as part of rake qunit:test","fancy_title":"Plugin QUnit tests are not running as part of rake qunit:test","slug":"plugin-qunit-tests-are-not-running-as-part-of-rake-qunit-test","posts_count":8,"reply_count":5,"highest_post_number":8,"created_at":"2017-03-20T20:29:06.536Z","last_posted_at":"2017-07-17T18:26:45.188Z","bumped":true,"bumped_at":"2017-07-17T18:26:45.188Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":181856,"title":"How does the Discourse team do usability testing?","fancy_title":"How does the Discourse team do usability testing?","slug":"how-does-the-discourse-team-do-usability-testing","posts_count":5,"reply_count":2,"highest_post_number":6,"created_at":"2021-03-03T18:09:29.357Z","last_posted_at":"2021-03-04T15:23:46.571Z","bumped":true,"bumped_at":"2021-03-04T15:23:46.571Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":3,"has_accepted_answer":false},{"id":108110,"title":"Advice on writing Ruby tests for plugins","fancy_title":"Advice on writing Ruby tests for plugins","slug":"advice-on-writing-ruby-tests-for-plugins","posts_count":15,"reply_count":13,"highest_post_number":15,"created_at":"2019-02-01T09:52:27.051Z","last_posted_at":"2019-05-03T03:54:47.163Z","bumped":true,"bumped_at":"2019-05-03T03:54:47.163Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":57292,"title":"Acceptance test content is present on page","fancy_title":"Acceptance test content is present on page","slug":"acceptance-test-content-is-present-on-page","posts_count":7,"reply_count":3,"highest_post_number":7,"created_at":"2017-02-13T02:48:10.108Z","last_posted_at":"2017-02-14T05:55:31.520Z","bumped":true,"bumped_at":"2017-02-14T05:55:31.520Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":291039,"title":"Google Search Console/Schema Markup test tool \"Google Rich Results Test\": mobile-view instead of crawler-view","fancy_title":"Google Search Console/Schema Markup test tool “Google Rich Results Test”: mobile-view instead of crawler-view","slug":"google-search-console-schema-markup-test-tool-google-rich-results-test-mobile-view-instead-of-crawler-view","posts_count":3,"reply_count":1,"highest_post_number":3,"created_at":"2024-01-09T16:53:57.797Z","last_posted_at":"2024-01-09T17:17:09.039Z","bumped":true,"bumped_at":"2024-01-09T17:17:09.039Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":1,"has_accepted_answer":false},{"id":267737,"title":"Experiments with Model-Based Testing","fancy_title":"Experiments with Model-Based Testing","slug":"experiments-with-model-based-testing","posts_count":1,"reply_count":0,"highest_post_number":1,"created_at":"2023-06-08T22:35:51.784Z","last_posted_at":"2023-06-08T22:35:51.914Z","bumped":true,"bumped_at":"2023-06-08T22:35:51.914Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":233389,"title":"Qunit tests not deterministic in Plugin?","fancy_title":"Qunit tests not deterministic in Plugin?","slug":"qunit-tests-not-deterministic-in-plugin","posts_count":2,"reply_count":0,"highest_post_number":2,"created_at":"2022-07-20T08:48:28.757Z","last_posted_at":"2022-07-20T11:03:42.522Z","bumped":true,"bumped_at":"2022-07-20T11:03:42.522Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":135876,"title":"Issues migrating test database","fancy_title":"Issues migrating test database","slug":"issues-migrating-test-database","posts_count":9,"reply_count":1,"highest_post_number":9,"created_at":"2019-12-12T21:01:31.303Z","last_posted_at":"2021-03-02T16:44:20.120Z","bumped":true,"bumped_at":"2021-03-02T16:44:20.120Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":175413,"title":"Acceptance Test for Markdown Extension?","fancy_title":"Acceptance Test for Markdown Extension?","slug":"acceptance-test-for-markdown-extension","posts_count":3,"reply_count":0,"highest_post_number":4,"created_at":"2021-01-07T19:56:04.931Z","last_posted_at":"2021-01-10T11:26:16.888Z","bumped":true,"bumped_at":"2021-01-10T16:30:56.164Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":150786,"title":"Auto migrate test database schema","fancy_title":"Auto migrate test database schema","slug":"auto-migrate-test-database-schema","posts_count":2,"reply_count":0,"highest_post_number":2,"created_at":"2020-05-08T04:27:56.739Z","last_posted_at":"2020-05-08T13:15:45.247Z","bumped":true,"bumped_at":"2020-05-08T13:15:45.247Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":118639,"title":"Building Tests for New discourse_api Endpoints","fancy_title":"Building Tests for New discourse_api Endpoints","slug":"building-tests-for-new-discourse-api-endpoints","posts_count":20,"reply_count":9,"highest_post_number":21,"created_at":"2019-05-24T22:21:24.896Z","last_posted_at":"2019-10-02T21:13:35.140Z","bumped":true,"bumped_at":"2019-10-02T21:36:26.639Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":126025,"title":"Seed or API calls to create test users","fancy_title":"Seed or API calls to create test users","slug":"seed-or-api-calls-to-create-test-users","posts_count":7,"reply_count":5,"highest_post_number":7,"created_at":"2019-08-17T02:13:06.578Z","last_posted_at":"2019-08-19T11:50:36.137Z","bumped":true,"bumped_at":"2019-08-19T11:50:36.137Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":71118,"title":"Smoke-testing plugins during upgrade process","fancy_title":"Smoke-testing plugins during upgrade process","slug":"smoke-testing-plugins-during-upgrade-process","posts_count":22,"reply_count":17,"highest_post_number":22,"created_at":"2017-10-01T08:18:48.067Z","last_posted_at":"2017-10-02T23:44:59.852Z","bumped":true,"bumped_at":"2017-10-02T23:44:59.852Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":["pr-welcome"],"tags_descriptions":{"pr-welcome":"You're welcome to submit a Github pull request that implements this"},"category_id":2,"has_accepted_answer":false},{"id":62797,"title":"QUnit tests won't pass in discourse_dev docker image","fancy_title":"QUnit tests won’t pass in discourse_dev docker image","slug":"qunit-tests-wont-pass-in-discourse-dev-docker-image","posts_count":20,"reply_count":11,"highest_post_number":21,"created_at":"2017-05-16T10:28:58.397Z","last_posted_at":"2017-07-25T15:50:27.716Z","bumped":true,"bumped_at":"2017-07-25T15:50:27.716Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":42947,"title":"Testing a Discourse API client on Travis-CI","fancy_title":"Testing a Discourse API client on Travis-CI","slug":"testing-a-discourse-api-client-on-travis-ci","posts_count":5,"reply_count":3,"highest_post_number":5,"created_at":"2016-04-21T19:15:00.130Z","last_posted_at":"2016-04-22T03:03:47.976Z","bumped":true,"bumped_at":"2016-04-22T03:03:47.976Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false},{"id":197887,"title":"PostMenu's '_extraButtons' isn't reset in between acceptance tests","fancy_title":"PostMenu’s ‘_extraButtons’ isn’t reset in between acceptance tests","slug":"postmenus-extrabuttons-isnt-reset-in-between-acceptance-tests","posts_count":2,"reply_count":0,"highest_post_number":4,"created_at":"2021-07-22T16:57:05.803Z","last_posted_at":"2021-08-04T09:11:29.538Z","bumped":true,"bumped_at":"2021-08-04T09:11:29.538Z","archetype":"regular","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"tags":[],"tags_descriptions":{},"category_id":7,"has_accepted_answer":false}],"users":[],"categories":[],"tags":[],"groups":[],"grouped_search_result":{"more_posts":null,"more_users":null,"more_categories":null,"term":"testing","search_log_id":2089836,"more_full_page_results":true,"can_create_topic":false,"error":null,"post_ids":[218354,138484,1381521,311252,59431,1419473,722424,266441,582008,1421414,1204506,1339808,1211823,1408389,443129,1412219,1160803,725228,1240758,1197679,1103913,722608,679950,1033333,498559,596557,583541,569209,1090520,860593,960683,1389188,1329017,421687,272436,899205,530438,260691,1432567,1305996,1135474,672801,871105,747613,583961,621707,334186,288991,187533,966756],"user_ids":[],"category_ids":[],"tag_ids":[],"group_ids":[],"extra":{"categories":[{"id":67,"name":"announcements","color":"ED207B","text_color":"FFFFFF","slug":"announcements","topic_count":312,"post_count":3973,"position":0,"description":"The place for all Discourse announcements.","description_text":"The place for all Discourse announcements.","description_excerpt":"The place for all Discourse announcements.","topic_url":"/t/about-the-announcements-category/68629","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":true,"sort_order":null,"sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":true,"custom_fields":{"has_chat_enabled":null,"activity_pub_ready":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_enabled":true,"activity_pub_username":"announcements","activity_pub_name":"Announcements","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":["new-feature","security"],"allowed_tag_groups":[],"allow_global_tags":true,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":true,"activity_pub_ready":true,"activity_pub_actor":{"id":2,"handle":"announcements@meta.discourse.org","name":"Announcements"},"activity_pub_username":"announcements","activity_pub_name":"Announcements","activity_pub_default_visibility":"public","activity_pub_publication_type":"first_post","activity_pub_post_object_type":"Note","uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":13,"name":"blog","color":"8080ff","text_color":"FFFFFF","slug":"blog","topic_count":170,"post_count":1477,"position":1,"description":"Discussion topics generated from the official Discourse Blog. These topics are linked from the bottom of each blog entry where the blog comments would normally be.","description_text":"Discussion topics generated from the official Discourse Blog. These topics are linked from the bottom of each blog entry where the blog comments would normally be.","description_excerpt":"Discussion topics generated from the official Discourse Blog. These topics are linked from the bottom of each blog entry where the blog comments would normally be.","topic_url":"/t/about-the-blog-category/5250","read_restricted":false,"permission":null,"parent_category_id":67,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"created","sort_ascending":false,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":10,"name":"documentation","color":"00A94F","text_color":"FFFFFF","slug":"documentation","topic_count":2,"post_count":2,"position":2,"description":"Documentation for how to use Discourse, install and configure sites, and troubleshoot common issues. Includes topics for tutorials, how-tos, general reference and troubleshooting. Topics need to be created in one of the subcategories, and may only be created by trust level 2 and up.","description_text":"Documentation for how to use Discourse, install and configure sites, and troubleshoot common issues. Includes topics for tutorials, how-tos, general reference and troubleshooting. Topics need to be created in one of the subcategories, and may only be created by trust level 2 and up.","description_excerpt":"Documentation for how to use Discourse, install and configure sites, and troubleshoot common issues. Includes topics for tutorials, how-tos, general reference and troubleshooting. Topics need to be created in one of the subcategories, and may only be created by trust level 2 and up.","topic_url":"/t/about-the-documentation-category/2629","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":true,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"top","subcategory_list_style":"boxes","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":true,"custom_fields":{"has_chat_enabled":null,"activity_pub_ready":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Article","activity_pub_publication_type":"full_topic","activity_pub_username":"documentation","activity_pub_enabled":true,"activity_pub_name":"Documentation"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":true,"activity_pub_ready":true,"activity_pub_actor":{"id":31769,"handle":"documentation@meta.discourse.org","name":"Documentation"},"activity_pub_username":"documentation","activity_pub_name":"Documentation","activity_pub_default_visibility":"public","activity_pub_publication_type":"full_topic","activity_pub_post_object_type":"Article","uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":53,"name":"admins","color":"F15D22","text_color":"FFFFFF","slug":"admins","topic_count":222,"post_count":1703,"position":3,"description":"Guides for Discourse admins and community managers with admin access.","description_text":"Guides for Discourse admins and community managers with admin access.","description_excerpt":"Guides for Discourse admins and community managers with admin access.","topic_url":"/t/about-the-admins-category/56207","read_restricted":false,"permission":null,"parent_category_id":10,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":true,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":["hosted-support","migrations"],"allowed_tag_groups":[],"allow_global_tags":true,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":125,"name":"moderators","color":"40d0e2","text_color":"FFFFFF","slug":"moderators","topic_count":16,"post_count":71,"position":4,"description":"Documentation for moderators and community managers using Discourse.","description_text":"Documentation for moderators and community managers using Discourse.","description_excerpt":"Documentation for moderators and community managers using Discourse.","topic_url":"/t/about-the-moderators-category/238588","read_restricted":false,"permission":null,"parent_category_id":10,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":126,"name":"users","color":"0088CC","text_color":"FFFFFF","slug":"users","topic_count":53,"post_count":184,"position":5,"description":"Documentation for all members of communities running Discourse.","description_text":"Documentation for all members of communities running Discourse.","description_excerpt":"Documentation for all members of communities running Discourse.","topic_url":"/t/about-the-users-category/238917","read_restricted":false,"permission":null,"parent_category_id":10,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":55,"name":"sysadmin","color":"E9DD00","text_color":"FFFFFF","slug":"sysadmin","topic_count":175,"post_count":2803,"position":6,"description":"Documentation for self-hosters and Discourse system administrators.","description_text":"Documentation for self-hosters and Discourse system administrators.","description_excerpt":"Documentation for self-hosters and Discourse system administrators.","topic_url":"/t/about-the-sysadmin-category/56209","read_restricted":false,"permission":null,"parent_category_id":10,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":true,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":["migrations"],"allowed_tag_groups":[],"allow_global_tags":true,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":127,"name":"theme developers","color":"92278F","text_color":"FFFFFF","slug":"theme-developers","topic_count":41,"post_count":215,"position":7,"description":"Documentation for developing themes and components that can be installed by admins.","description_text":"Documentation for developing themes and components that can be installed by admins.","description_excerpt":"Documentation for developing themes and components that can be installed by admins.","topic_url":"/t/about-the-theme-developers-category/239285","read_restricted":false,"permission":null,"parent_category_id":10,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":56,"name":"developers","color":"00A94F","text_color":"FFFFFF","slug":"devs","topic_count":96,"post_count":1185,"position":8,"description":"Documentation for developing features, plugins, or integrations with Discourse.","description_text":"Documentation for developing features, plugins, or integrations with Discourse.","description_excerpt":"Documentation for developing features, plugins, or integrations with Discourse.","topic_url":"/t/about-the-developers-category/56210","read_restricted":false,"permission":null,"parent_category_id":10,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":true,"custom_fields":{"has_chat_enabled":null,"activity_pub_ready":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Article","activity_pub_enabled":true,"activity_pub_username":"developer-docs","activity_pub_name":"Developer Documentation","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":true,"activity_pub_ready":true,"activity_pub_actor":{"id":35545,"handle":"developer-docs@meta.discourse.org","name":"Developer Documentation"},"activity_pub_username":"developer-docs","activity_pub_name":"Developer Documentation","activity_pub_default_visibility":"public","activity_pub_publication_type":"first_post","activity_pub_post_object_type":"Article","uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":6,"name":"support","color":"CEA9A9","text_color":"FFFFFF","slug":"support","topic_count":15841,"post_count":103687,"position":9,"description":"The category for general support questions on using your Discourse site.","description_text":"The category for general support questions on using your Discourse site.","description_excerpt":"The category for general support questions on using your Discourse site.","topic_url":"/t/about-the-support-category/389","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"\u003e Before asking, did you search first? Press 🔍 at the upper right to search.","has_children":true,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_accepted_answers":"true","enable_unassigned_filter":"false","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":21,"name":"wordpress","color":"F9CFCF","text_color":"FFFFFF","slug":"wordpress","topic_count":732,"post_count":5110,"position":10,"description":"Support for the official Discourse WordPress plugin at \u003ca href=\"https://github.com/discourse/wp-discourse\" class=\"inline-onebox\"\u003eGitHub - discourse/wp-discourse: WordPress plugin that lets you use Discourse as the community engine for a WordPress blog\u003c/a\u003e","description_text":"Support for the official Discourse WordPress plugin at GitHub - discourse/wp-discourse: WordPress plugin that lets you use Discourse as the community engine for a WordPress blog","description_excerpt":"Support for the official Discourse WordPress plugin at \u003ca href=\"https://github.com/discourse/wp-discourse\" class=\"inline-onebox\"\u003eGitHub - discourse/wp-discourse: WordPress plugin that lets you use Discourse as the community engine for a WordPress blog\u003c/a\u003e","topic_url":"/t/about-the-wordpress-category/12282","read_restricted":false,"permission":null,"parent_category_id":6,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":"true","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":1,"name":"bug","color":"e9dd00","text_color":"000000","slug":"bug","topic_count":5153,"post_count":35973,"position":11,"description":"A bug report means \u003cstrong\u003esomething is broken, preventing normal/typical use of Discourse\u003c/strong\u003e. Do be sure to search prior to submitting bugs. \u003ca href=\"https://meta.discourse.org/t/how-to-write-a-good-bug-report/183671/\"\u003eInclude repro steps\u003c/a\u003e, and only describe one bug per topic please.","description_text":"A bug report means something is broken, preventing normal/typical use of Discourse. Do be sure to search prior to submitting bugs. Include repro steps, and only describe one bug per topic please.","description_excerpt":"A bug report means something is broken, preventing normal/typical use of Discourse. Do be sure to search prior to submitting bugs. \u003ca href=\"https://meta.discourse.org/t/how-to-write-a-good-bug-report/183671/\"\u003eInclude repro steps\u003c/a\u003e, and only describe one bug per topic please.","topic_url":"/t/about-the-bug-category/2","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_accepted_answers":null,"enable_unassigned_filter":"true","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":true,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":9,"name":"ux","color":"5F497A","text_color":"FFFFFF","slug":"ux","topic_count":2700,"post_count":18225,"position":12,"description":"Discussion about the user interface of Discourse and how features are presented (including language and UI elements).","description_text":"Discussion about the user interface of Discourse and how features are presented (including language and UI elements).","description_excerpt":"Discussion about the user interface of Discourse and how features are presented (including language and UI elements).","topic_url":"/t/about-the-ux-category/2628","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":2,"name":"feature","color":"0E76BD","text_color":"FFFFFF","slug":"feature","topic_count":6997,"post_count":58067,"position":13,"description":"Discussion about existing Discourse features, how they can be improved or enhanced, and how proposed new features could work.","description_text":"Discussion about existing Discourse features, how they can be improved or enhanced, and how proposed new features could work.","description_excerpt":"Discussion about existing Discourse features, how they can be improved or enhanced, and how proposed new features could work.","topic_url":"/t/about-the-feature-category/11","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_ready":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_username":"feature","activity_pub_name":"Feature Requests","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post","activity_pub_enabled":true},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":true,"activity_pub_ready":true,"activity_pub_actor":{"id":1,"handle":"feature@meta.discourse.org","name":"Feature Requests"},"activity_pub_username":"feature","activity_pub_name":"Feature Requests","activity_pub_default_visibility":"public","activity_pub_publication_type":"first_post","activity_pub_post_object_type":"Note","uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":148,"name":"data \u0026 reporting","color":"D24899","text_color":"FFFFFF","slug":"data-reporting","topic_count":631,"post_count":3056,"position":14,"description":"This is the category for everything related to Discourse data and reporting. Here, you can discuss dashboard reports, custom badge queries, data-explorer queries and any other analytic add-ons.","description_text":"This is the category for everything related to Discourse data and reporting. Here, you can discuss dashboard reports, custom badge queries, data-explorer queries and any other analytic add-ons.","description_excerpt":"This is the category for everything related to Discourse data and reporting. Here, you can discuss dashboard reports, custom badge queries, data-explorer queries and any other analytic add-ons.","topic_url":"/t/about-the-data-reporting-category/274664","read_restricted":false,"permission":null,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","enable_accepted_answers":"true","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":24,"name":"sso","color":"d47711","text_color":"FFFFFF","slug":"sso","topic_count":493,"post_count":2546,"position":15,"description":"For queries specifically about SSO (single sign-on) and login using third-party providers (Google, Facebook, GitHub etc). See the \u003ca href=\"https://meta.discourse.org/t/discourseconnect-official-single-sign-on-for-discourse-sso/13045\"\u003eofficial documentation on DiscourseConnect SSO\u003c/a\u003e.","description_text":"For queries specifically about SSO (single sign-on) and login using third-party providers (Google, Facebook, GitHub etc). See the official documentation on DiscourseConnect SSO.","description_excerpt":"For queries specifically about SSO (single sign-on) and login using third-party providers (Google, Facebook, GitHub etc). See the \u003ca href=\"https://meta.discourse.org/t/discourseconnect-official-single-sign-on-for-discourse-sso/13045\"\u003eofficial documentation on DiscourseConnect SSO\u003c/a\u003e.","topic_url":"/t/about-the-sso-category/13110","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":"true","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":65,"name":"community","color":"12A89D","text_color":"FFFFFF","slug":"community","topic_count":866,"post_count":8844,"position":16,"description":"A great platform doesn’t guarantee success. Community building is a science. This category is for discussions about launching, building, growing and managing a thriving community.","description_text":"A great platform doesn’t guarantee success. Community building is a science. This category is for discussions about launching, building, growing and managing a thriving community.","description_excerpt":"A great platform doesn’t guarantee success. Community building is a science. This category is for discussions about launching, building, growing and managing a thriving community.","topic_url":"/t/about-the-community-category/67750","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":7,"name":"dev","color":"292929","text_color":"fff","slug":"dev","topic_count":3467,"post_count":20210,"position":17,"description":"The category for all things Discourse Development. Building a customization for yourself or the community? Then this is the category for you!","description_text":"The category for all things Discourse Development. Building a customization for yourself or the community? Then this is the category for you!","description_excerpt":"The category for all things Discourse Development. Building a customization for yourself or the community? Then this is the category for you!","topic_url":"/t/about-the-dev-category/1026","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":true,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"boxes","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":"true","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":27,"name":"translations","color":"808281","text_color":"FFFFFF","slug":"translations","topic_count":297,"post_count":1832,"position":18,"description":"This category is for discussion about localizing Discourse.","description_text":"This category is for discussion about localizing Discourse.","description_excerpt":"This category is for discussion about localizing Discourse.","topic_url":"/t/about-the-translations-category/14549","read_restricted":false,"permission":null,"parent_category_id":7,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":14,"name":"marketplace","color":"8C6238","text_color":"FFFFFF","slug":"marketplace","topic_count":1162,"post_count":5865,"position":19,"description":"This is your hub for all Discourse-related commerce: jobs, gigs, plugins, themes, hosting, and more.","description_text":"This is your hub for all Discourse-related commerce: jobs, gigs, plugins, themes, hosting, and more.","description_excerpt":"This is your hub for all Discourse-related commerce: jobs, gigs, plugins, themes, hosting, and more.","topic_url":"/t/about-the-marketplace-category/5425","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"What would you like done?\n\nWhen do you need it done?\n\nWhat is your budget, in $ USD that you can offer for this task?\n\n\u003c!-- We encourage caution and due diligence when engaging with potential contractors or clients. Verify their credentials, check previous work, and ensure a transparent and legitimate transaction. Always remember, your safety and security in the marketplace is your responsibility. --\u003e","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":["delivered"],"allowed_tag_groups":[],"allow_global_tags":true,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":22,"name":"plugin","color":"F7941D","text_color":"FFFFFF","slug":"plugin","topic_count":315,"post_count":10429,"position":20,"description":"A directory of Discourse plugins, both official and third-party.","description_text":"A directory of Discourse plugins, both official and third-party.","description_excerpt":"A directory of Discourse plugins, both official and third-party.","topic_url":"/t/about-the-plugin-category/12648","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"| | | |\n| - | - | - |\n| :information_source: | **Summary** | In a few words, what does this plugin do? |\n| :hammer_and_wrench: | **Repository Link** | \u003c\u003e |\n| :open_book: | **Install Guide** | [How to install plugins in Discourse](https://meta.discourse.org/t/install-plugins-in-discourse/19157) |\n\n\u003cbr\u003e \n\n### Features\n \nDescribe the major features of the plugin\n \n### Configuration\n \nInclude detailed steps on how to configure the plugin (include screenshots where necessary)\n \n### CHANGELOG\n- Add new bullets when major features are committed here\n \n### TODO\n- Add any #pr-welcome TODO tasks here","has_children":true,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"boxes","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":["Resource Status"],"allow_global_tags":true,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":5,"name":"extras","color":"25AAE2","text_color":"FFFFFF","slug":"extras","topic_count":90,"post_count":985,"position":21,"description":"A directory of all extensions \u0026amp; integrations for Discourse which are \u003cem\u003enot\u003c/em\u003e Discourse plugins, i.e. a CMS plugin, a browser extension or a native application.","description_text":"A directory of all extensions \u0026amp; integrations for Discourse which are not Discourse plugins, i.e. a CMS plugin, a browser extension or a native application.","description_excerpt":"A directory of all extensions \u0026amp; integrations for Discourse which are not Discourse plugins, i.e. a CMS plugin, a browser extension or a native application.","topic_url":"/t/about-the-extras-category/28","read_restricted":false,"permission":null,"parent_category_id":22,"notification_level":1,"topic_template":"","has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":["Resource Status"],"allow_global_tags":true,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":61,"name":"theme","color":"E43D30","text_color":"FFFFFF","slug":"theme","topic_count":66,"post_count":2138,"position":22,"description":"Themes are expansive customizations that change multiple elements of the style of your forum design, and often also include additional front-end features.","description_text":"Themes are expansive customizations that change multiple elements of the style of your forum design, and often also include additional front-end features.","description_excerpt":"Themes are expansive customizations that change multiple elements of the style of your forum design, and often also include additional front-end features.","topic_url":"/t/about-the-theme-category/60925","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"||||\n|-|-|-|\n| :information_source: | **Summary** | ADD SHORT SUMMARY \n| :eyeglasses:|**Preview**| PREVIEW_LINK |\n| :hammer_and_wrench:|**Repository**| REPOSITORY_LINK |\n| :question:|**Install Guide**|[How to install a theme or theme component](https://meta.discourse.org/t/how-do-i-install-a-theme-or-theme-component/63682)|\n| :open_book:|**New to Discourse Themes?**| [Beginner’s guide to using Discourse Themes](https://meta.discourse.org/t/beginners-guide-to-using-discourse-themes/91966)\n\n\u003c!-- Describe this theme in one or two sentences --\u003e\n\nShort description...\n\n\u003c!-- Add screenshots (if applicable) --\u003e\n\nScreenshots...\n\n\u003c!-- Add more details and explain the settings (if applicable) --\u003e\n\nDetailed description...\n","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"none","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":["color-palette"],"allowed_tag_groups":["Resource Status"],"allow_global_tags":true,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":120,"name":"theme-component","color":"1dedf8","text_color":"FFFFFF","slug":"theme-component","topic_count":300,"post_count":7516,"position":23,"description":"Theme components are customizations that change surface elements of your forum design, or add extra front-end features.","description_text":"Theme components are customizations that change surface elements of your forum design, or add extra front-end features.","description_excerpt":"Theme components are customizations that change surface elements of your forum design, or add extra front-end features.","topic_url":"/t/about-the-theme-component-category/232731","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"| | | |\n| - | - | - |\n| :information_source: | **Summary** | ADD SHORT SUMMARY |\n| :eyeglasses: |**Preview**| PREVIEW_LINK |\n| :hammer_and_wrench: | **Repository**| REPOSITORY_LINK |\n| :question: | **Install Guide** | [How to install a theme or theme component](https://meta.discourse.org/t/how-do-i-install-a-theme-or-theme-component/63682) |\n| :open_book: | **New to Discourse Themes?** | [Beginner’s guide to using Discourse Themes](https://meta.discourse.org/t/beginners-guide-to-using-discourse-themes/91966) |\n\n\u003c!-- Fill in \"repoName\" and \"repoURL\" for the automatic install button --\u003e\n\n[wrap=theme-install-button repoName=\"Component's name\" repoUrl=\"GitHub repository link\"]\nInstall this theme component\n[/wrap]\n\n\u003c!-- Describe this theme/component in one or two sentences --\u003e\n\nShort description...\n\n\u003c!-- Add screenshots (if applicable) --\u003e\n\nScreenshots...\n\n\u003c!-- Add more details and explain the settings (if applicable) --\u003e\n\nDetailed description...","has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"none","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":["Resource Status"],"allow_global_tags":true,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":31,"name":"installation","color":"997E7E","text_color":"FFFFFF","slug":"installation","topic_count":3426,"post_count":29611,"position":24,"description":"Getting Discourse up and running, keeping it going, upgrading, and any other general sysadmin maintenance.","description_text":"Getting Discourse up and running, keeping it going, upgrading, and any other general sysadmin maintenance.","description_excerpt":"Getting Discourse up and running, keeping it going, upgrading, and any other general sysadmin maintenance.","topic_url":"/t/about-the-installation-category/21019","read_restricted":false,"permission":null,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":"true","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":106,"name":"migration","color":"652D90","text_color":"FFFFFF","slug":"migration","topic_count":215,"post_count":1558,"position":25,"description":"You want to migrate your community to Discourse? Awesome! This is the category where you can ask questions, get help, and document your Discourse migration journey.","description_text":"You want to migrate your community to Discourse? Awesome! This is the category where you can ask questions, get help, and document your Discourse migration journey.","description_excerpt":"You want to migrate your community to Discourse? Awesome! This is the category where you can ask questions, get help, and document your Discourse migration journey.","topic_url":"/t/about-the-migration-category/196969","read_restricted":false,"permission":null,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":"true","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":["Migration"],"allow_global_tags":true,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":8,"name":"hosting","color":"00AEEF","text_color":"FFFFFF","slug":"hosting","topic_count":501,"post_count":4117,"position":26,"description":"Topics about hosting Discourse, either on your own servers, in the cloud, or with specific hosting services.","description_text":"Topics about hosting Discourse, either on your own servers, in the cloud, or with specific hosting services.","description_excerpt":"Topics about hosting Discourse, either on your own servers, in the cloud, or with specific hosting services.","topic_url":"/t/about-the-hosting-category/2626","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post","enable_accepted_answers":"true"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":30,"name":"releases","color":"BF1E2E","text_color":"FFFFFF","slug":"releases","topic_count":23,"post_count":104,"position":27,"description":"Outlining each official release of Discourse, and plans for future releases.","description_text":"Outlining each official release of Discourse, and plans for future releases.","description_excerpt":"Outlining each official release of Discourse, and plans for future releases.","topic_url":"/t/about-the-releases-category/20857","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"created","sort_ascending":false,"show_subcategory_list":true,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":3,"name":"site feedback","color":"888","text_color":"FFFFFF","slug":"site-feedback","topic_count":417,"post_count":3220,"position":28,"description":"Discussion about \u003ca href=\"http://meta.discourse.org\"\u003emeta.discourse.org\u003c/a\u003e itself - the organization of this forum, how it works, and how we can improve this site.","description_text":"Discussion about meta.discourse.org itself - the organization of this forum, how it works, and how we can improve this site.","description_excerpt":"Discussion about \u003ca href=\"http://meta.discourse.org\"\u003emeta.discourse.org\u003c/a\u003e itself - the organization of this forum, how it works, and how we can improve this site.","topic_url":"/t/about-the-site-feedback-category/24","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":true,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":152,"name":"theme feedback","color":"ED207B","text_color":"FFFFFF","slug":"theme-feedback","topic_count":11,"post_count":44,"position":29,"description":"This is the category to gather all the UX reports for \u003ca href=\"https://meta.discourse.org/t/we-have-a-new-default-theme-here-on-meta/284692\"\u003eour new theme on meta\u003c/a\u003e. It also uses the new Form Templates feature that I’ve been wanting to try out.","description_text":"This is the category to gather all the UX reports for our new theme on meta. It also uses the new Form Templates feature that I’ve been wanting to try out.","description_excerpt":"This is the category to gather all the UX reports for \u003ca href=\"https://meta.discourse.org/t/we-have-a-new-default-theme-here-on-meta/284692\"\u003eour new theme on meta\u003c/a\u003e. It also uses the new Form Templates feature that I’ve been wanting to try out.","topic_url":"/t/about-the-theme-feedback-category/284904","read_restricted":false,"permission":null,"parent_category_id":3,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[1],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":157,"name":"forum summaries","color":"72e9a7","text_color":"FFFFFF","slug":"forum-summaries","topic_count":4,"post_count":36,"position":30,"description":"Stay up-to-date with the pulse of our community through AI-crafted summaries. This category harnesses the power of artificial intelligence to collate and condense forum activities, providing you with comprehensive yet succinct overviews. From emerging discussions to trending topics, our AI summaries deliver the essence of the Discourse community’s heartbeat, right at your fingertips.","description_text":"Stay up-to-date with the pulse of our community through AI-crafted summaries. This category harnesses the power of artificial intelligence to collate and condense forum activities, providing you with comprehensive yet succinct overviews. From emerging discussions to trending topics, our AI summaries deliver the essence of the Discourse community’s heartbeat, right at your fingertips.","description_excerpt":"Stay up-to-date with the pulse of our community through AI-crafted summaries. This category harnesses the power of artificial intelligence to collate and condense forum activities, providing you with comprehensive yet succinct overviews. From emerging discussions to trending topics, our AI summarie\u0026hellip;","topic_url":"/t/about-the-forum-summaries-category/291765","read_restricted":false,"permission":null,"parent_category_id":3,"notification_level":0,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":35,"name":"praise","color":"9EB83B","text_color":"FFFFFF","slug":"praise","topic_count":294,"post_count":1091,"position":31,"description":"Have something nice to say about Discourse?","description_text":"Have something nice to say about Discourse?","description_excerpt":"Have something nice to say about Discourse?","topic_url":"/t/about-the-praise-category/30010","read_restricted":false,"permission":null,"notification_level":1,"topic_template":null,"has_children":true,"sort_order":null,"sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":63,"name":"comparison","color":"F1592A","text_color":"FFFFFF","slug":"comparison","topic_count":11,"post_count":137,"position":32,"description":"Topics comparing Discourse to other platforms.","description_text":"Topics comparing Discourse to other platforms.","description_excerpt":"Topics comparing Discourse to other platforms.","topic_url":"/t/about-the-comparison-category/65736","read_restricted":false,"permission":null,"parent_category_id":35,"notification_level":1,"topic_template":"### About PLATFORM_NAME\n\nA blurb about the platform being compared to Discourse. \n\nhttp://link.to.website\n\n ### Previous discussions:\n\nlinks to previous discussions related\n\n### Importer status\n\nIs there an official Discourse importer? Where is it? ","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":105,"name":"community support program","color":"92278F","text_color":"FFFFFF","slug":"support-program","topic_count":4,"post_count":27,"position":33,"description":"Get recognition for your work in helping and supporting Discourse communities by joining our Community Support Program.","description_text":"Get recognition for your work in helping and supporting Discourse communities by joining our Community Support Program.","description_excerpt":"Get recognition for your work in helping and supporting Discourse communities by joining our Community Support Program.","topic_url":"/t/about-the-community-support-program-category/193906","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":true,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":124,"name":"General","color":"25AAE2","text_color":"FFFFFF","slug":"general","topic_count":155,"post_count":1408,"position":35,"description":"Create topics here that don’t fit into any other existing category.","description_text":"Create topics here that don’t fit into any other existing category.","description_excerpt":"Create topics here that don’t fit into any other existing category.","topic_url":"/t/about-the-general-category/237517","read_restricted":false,"permission":null,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":17,"name":"Uncategorized","color":"AB9364","text_color":"FFFFFF","slug":"uncategorized","topic_count":0,"post_count":0,"position":77,"description":"Topics that don't need a category, or don't fit into any other existing category.","description_text":"Topics that don't need a category, or don't fit into any other existing category.","description_excerpt":"Topics that don't need a category, or don't fit into any other existing category.","topic_url":"/t/","read_restricted":false,"permission":null,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false}]}}} \ No newline at end of file diff --git a/spec/fixtures/search_meta/site.json b/spec/fixtures/search_meta/site.json index 9651e0ce8..78f5fe397 100644 --- a/spec/fixtures/search_meta/site.json +++ b/spec/fixtures/search_meta/site.json @@ -1 +1 @@ -{"default_archetype":"regular","notification_types":{"mentioned":1,"replied":2,"quoted":3,"edited":4,"liked":5,"private_message":6,"invited_to_private_message":7,"invitee_accepted":8,"posted":9,"moved_post":10,"linked":11,"granted_badge":12,"invited_to_topic":13,"custom":14,"group_mentioned":15,"group_message_summary":16,"watching_first_post":17,"topic_reminder":18,"liked_consolidated":19,"post_approved":20,"code_review_commit_approved":21,"membership_request_accepted":22,"membership_request_consolidated":23,"bookmark_reminder":24,"reaction":25,"votes_released":26,"event_reminder":27,"event_invitation":28,"chat_mention":29,"chat_message":30,"chat_invitation":31,"chat_group_mention":32,"chat_quoted":33,"assigned":34,"question_answer_user_commented":35,"watching_category_or_tag":36,"new_features":37,"admin_problems":38,"following":800,"following_created_topic":801,"following_replied":802,"circles_activity":900},"post_types":{"regular":1,"moderator_action":2,"small_action":3,"whisper":4},"user_tips":{"first_notification":1,"topic_timeline":2,"post_menu":3,"topic_notification_levels":4,"suggested_topics":5,"admin_guide":6},"trust_levels":{"newuser":0,"basic":1,"member":2,"regular":3,"leader":4},"groups":[{"id":183,"name":"ai-personas","flair_url":null,"flair_bg_color":"","flair_color":""},{"id":160,"name":"community-moderators","flair_url":null,"flair_bg_color":"","flair_color":""},{"id":184,"name":"glimmer-search-menu","flair_url":null,"flair_bg_color":"","flair_color":""},{"id":157,"name":"new-new-testers","flair_url":null,"flair_bg_color":"","flair_color":""},{"id":48,"name":"plugin_authors","flair_url":"fa-plug","flair_bg_color":"dddddd","flair_color":"111111"},{"id":148,"name":"support-advocates","flair_url":"https://d11a6trkgmumsb.cloudfront.net/original/3X/e/4/e4038d4d9848de2eabab38e17b8bdb69da154024.svg","flair_bg_color":"FFFFFF","flair_color":""},{"id":151,"name":"support-enthusiasts","flair_url":"https://d11a6trkgmumsb.cloudfront.net/original/3X/1/3/13f5d8d7e56be8a6a1ea3de009b985a548aec8d4.svg","flair_bg_color":"FFFFFF","flair_color":""},{"id":142,"name":"support-experts","flair_url":"https://d11a6trkgmumsb.cloudfront.net/original/3X/e/2/e250ec403580530d19e6a9ed42d0d525a51a9dbe.svg","flair_bg_color":"FFFFFF","flair_color":""},{"id":118,"name":"support-explorers","flair_url":"https://d11a6trkgmumsb.cloudfront.net/original/4X/d/1/b/d1ba0acf09b9d01f87f9e05bbee1dc5b0e316d5f.png","flair_bg_color":"dddddd","flair_color":"111111"},{"id":47,"name":"team","flair_url":"https://d11a6trkgmumsb.cloudfront.net/original/3X/e/b/ebee30bd98aef20357e4a177a5a1e45b877ce088.svg","flair_bg_color":"","flair_color":"111"},{"id":73,"name":"theme_authors","flair_url":"fa-paint-brush","flair_bg_color":"ddd","flair_color":"111"},{"id":84,"name":"theme_creator","flair_url":"fa-palette","flair_bg_color":"ddd","flair_color":"111"},{"id":50,"name":"translators","flair_url":"fa-globe","flair_bg_color":"ddd","flair_color":"111"}],"filters":["latest","unread","new","unseen","top","read","posted","bookmarks","hot"],"periods":["all","yearly","quarterly","monthly","weekly","daily"],"top_menu_items":["latest","unread","new","unseen","top","read","posted","bookmarks","hot","categories"],"anonymous_top_menu_items":["latest","top","categories","hot","categories","top"],"uncategorized_category_id":17,"user_field_max_length":2048,"post_action_types":[{"id":2,"name_key":"like","name":"Like","description":"Like this post","short_description":"Like this post","is_flag":false,"is_custom_flag":false},{"id":3,"name_key":"off_topic","name":"Off-Topic","description":"This post is not relevant to the current discussion as defined by the title and first post, and should probably be moved elsewhere.","short_description":"Not relevant to the discussion","is_flag":true,"is_custom_flag":false},{"id":4,"name_key":"inappropriate","name":"Inappropriate","description":"This post contains content that a reasonable person would consider offensive, abusive, to be hateful conduct or a violation of \u003ca href=\"/guidelines\"\u003eour community guidelines\u003c/a\u003e.","short_description":"A violation of \u003ca href=\"/guidelines\"\u003eour community guidelines\u003c/a\u003e","is_flag":true,"is_custom_flag":false},{"id":8,"name_key":"spam","name":"Spam","description":"This post is an advertisement, or vandalism. It is not useful or relevant to the current topic.","short_description":"This is an advertisement or vandalism","is_flag":true,"is_custom_flag":false},{"id":6,"name_key":"notify_user","name":"Send @%{username} a message","description":"I want to talk to this person directly and personally about their post.","short_description":"I want to talk to this person directly and personally about their post.","is_flag":true,"is_custom_flag":true},{"id":10,"name_key":"illegal","name":"Illegal","description":"This post requires staff attention because I believe it contains content that is illegal.","short_description":"This is illegal","is_flag":true,"is_custom_flag":true},{"id":null,"name_key":null,"name":"Translation missing: en.post_action_types..title","description":"Translation missing: en.post_action_types.description","short_description":"Translation missing: en.post_action_types.short_description","is_flag":false,"is_custom_flag":false},{"id":7,"name_key":"notify_moderators","name":"Something Else","description":"This post requires staff attention for another reason not listed above.","short_description":"Requires staff attention for another reason","is_flag":true,"is_custom_flag":true}],"topic_flag_types":[{"id":4,"name_key":"inappropriate","name":"Inappropriate","description":"This topic contains content that a reasonable person would consider offensive, abusive, to be hateful conduct or a violation of \u003ca href=\"/guidelines\"\u003eour community guidelines\u003c/a\u003e.","short_description":"A violation of \u003ca href=\"/guidelines\"\u003eour community guidelines\u003c/a\u003e","is_flag":true,"is_custom_flag":false},{"id":8,"name_key":"spam","name":"Spam","description":"This topic is an advertisement. It is not useful or relevant to this site, but promotional in nature.","short_description":"This is an advertisement","is_flag":true,"is_custom_flag":false},{"id":10,"name_key":"illegal","name":"Illegal","description":"This topic requires staff attention because I believe it contains content that is illegal.","short_description":"This is illegal","is_flag":true,"is_custom_flag":true},{"id":null,"name_key":null,"name":"Translation missing: en.topic_flag_types..title","description":"Translation missing: en.topic_flag_types.description","short_description":"Translation missing: en.topic_flag_types.short_description","is_flag":false,"is_custom_flag":false},{"id":7,"name_key":"notify_moderators","name":"Something Else","description":"This topic requires general staff attention based on the \u003ca href=\"/guidelines\"\u003eguidelines\u003c/a\u003e, \u003ca href=\"/tos\"\u003eTOS\u003c/a\u003e, or for another reason not listed above.","short_description":"Requires staff attention for another reason","is_flag":true,"is_custom_flag":true}],"can_create_tag":false,"can_tag_topics":false,"can_tag_pms":false,"tags_filter_regexp":"[/\\?#\\[\\]@!\\$\u0026'\\(\\)\\*\\+,;=\\.%\\\\`^\\s|\\{\\}\"\u003c\u003e]+","top_tags":["how-to","chat","rest-api","sql-query","unsupported-install","email","pr-welcome","completed","ai","activity-summary","official","solved","sidebar","release-notes","server-resources","calendar-and-event","badges","topic-voting","docker","tags","advertising","invites","search","sql-triggered-badge","subscriptions","new-feature","chat-integration","wordpress","groups","themes","css"],"navigation_menu_site_top_tags":[{"name":"how-to","description":"How to guides contain steps to follow to solve a specific problem","pm_only":false},{"name":"chat","description":null,"pm_only":false},{"name":"rest-api","description":"Topics about making an external request to Discourse","pm_only":false},{"name":"sql-query","description":"SQL queries for the data-explorer","pm_only":false},{"name":"unsupported-install","description":null,"pm_only":false}],"topic_featured_link_allowed_category_ids":[149,120,122,157,121,153,127,9,136,138,61,83,96,56,132,123,144,104,10,27,137,25,124,22,134,24,110,105,143,119,145,135,129,7,30,89,131,150,6,155,31,20,133,55,102,148,147,95,152,151,128,154,21,139,108,69,3,14,125,63,2,126,115,106,1,118,117,17,53,13,5,65,8,67,35],"user_themes":[{"theme_id":272,"name":"Accessible contrast (dark)","default":false,"color_scheme_id":85},{"theme_id":271,"name":"Accessible contrast (light)","default":false,"color_scheme_id":84},{"theme_id":242,"name":"Air Theme","default":false,"color_scheme_id":82},{"theme_id":337,"name":"Central","default":false,"color_scheme_id":100},{"theme_id":335,"name":"CentralStaffOnly","default":false,"color_scheme_id":96},{"theme_id":31,"name":"Dark","default":false,"color_scheme_id":86},{"theme_id":140,"name":"Default","default":false,"color_scheme_id":34},{"theme_id":281,"name":"Default (full-width)","default":false,"color_scheme_id":34},{"theme_id":51,"name":"Discourse-classic","default":false,"color_scheme_id":34},{"theme_id":296,"name":"Fully","default":false,"color_scheme_id":null},{"theme_id":131,"name":"Ghost","default":false,"color_scheme_id":66},{"theme_id":80,"name":"Graceful","default":false,"color_scheme_id":54},{"theme_id":83,"name":"Grey Amber","default":false,"color_scheme_id":57},{"theme_id":232,"name":"Hidden Whispers","default":false,"color_scheme_id":null},{"theme_id":34,"name":"Material Design","default":false,"color_scheme_id":37},{"theme_id":331,"name":"Meta Branded","default":true,"color_scheme_id":34},{"theme_id":125,"name":"Minima Dark","default":false,"color_scheme_id":43},{"theme_id":299,"name":"redditish","default":false,"color_scheme_id":null},{"theme_id":30,"name":"Sam's Simple Theme","default":false,"color_scheme_id":null}],"user_color_schemes":[{"id":93,"name":"Central Dark","is_dark":true},{"id":95,"name":"Central Dark","is_dark":true},{"id":100,"name":"Central Light","is_dark":false},{"id":96,"name":"Central Light","is_dark":false},{"id":86,"name":"Dark","is_dark":true},{"id":34,"name":"Default Light","is_dark":false},{"id":87,"name":"Solarized Light","is_dark":false},{"id":85,"name":"WCAG Dark","is_dark":true},{"id":84,"name":"WCAG Light","is_dark":false}],"default_dark_color_scheme":null,"censored_regexp":[{"(?:\\P{L}|^)(EICARTESTCENSOR)(?=\\P{L}|$)":{"case_sensitive":false}}],"custom_emoji_translation":{},"watched_words_replace":{"(?:\\P{L}|^)(¯_\\(ツ\\)_/¯)(?=\\P{L}|$)":{"word":"¯_(ツ)_/¯","replacement":"¯\\_(ツ)_/¯","case_sensitive":false}},"watched_words_link":{"(?:\\P{L}|^)(Data Explorer)(?=\\P{L}|$)":{"word":"Data Explorer","replacement":"https://meta.discourse.org/t/32566?silent=true","case_sensitive":false},"(?:\\P{L}|^)(discourseconnect)(?=\\P{L}|$)":{"word":"discourseconnect","replacement":"https://meta.discourse.org/t/13045?silent=true","case_sensitive":false},"(?:\\P{L}|^)(discourse connect)(?=\\P{L}|$)":{"word":"discourse connect","replacement":"https://meta.discourse.org/t/13045?silent=true","case_sensitive":false},"(?:\\P{L}|^)(discourse sso)(?=\\P{L}|$)":{"word":"discourse sso","replacement":"https://meta.discourse.org/t/13045?silent=true","case_sensitive":false},"(?:\\P{L}|^)(official install)(?=\\P{L}|$)":{"word":"official install","replacement":"https://meta.discourse.org/t/142537?silent=true","case_sensitive":false},"(?:\\P{L}|^)(standard install)(?=\\P{L}|$)":{"word":"standard install","replacement":"https://meta.discourse.org/t/142537?silent=true","case_sensitive":false},"(?:\\P{L}|^)(safe mode)(?=\\P{L}|$)":{"word":"safe mode","replacement":"https://meta.discourse.org/t/53504?silent=true","case_sensitive":false}},"categories":[{"id":67,"name":"announcements","color":"ED207B","text_color":"FFFFFF","slug":"announcements","topic_count":312,"post_count":3973,"position":0,"description":"The place for all Discourse announcements.","description_text":"The place for all Discourse announcements.","description_excerpt":"The place for all Discourse announcements.","topic_url":"/t/about-the-announcements-category/68629","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":true,"sort_order":null,"sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":true,"custom_fields":{"has_chat_enabled":null,"activity_pub_ready":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_enabled":true,"activity_pub_username":"announcements","activity_pub_name":"Announcements","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":["new-feature","security"],"allowed_tag_groups":[],"allow_global_tags":true,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":true,"activity_pub_ready":true,"activity_pub_actor":{"id":2,"handle":"announcements@meta.discourse.org","name":"Announcements"},"activity_pub_username":"announcements","activity_pub_name":"Announcements","activity_pub_default_visibility":"public","activity_pub_publication_type":"first_post","activity_pub_post_object_type":"Note","uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":13,"name":"blog","color":"8080ff","text_color":"FFFFFF","slug":"blog","topic_count":170,"post_count":1477,"position":1,"description":"Discussion topics generated from the official Discourse Blog. These topics are linked from the bottom of each blog entry where the blog comments would normally be.","description_text":"Discussion topics generated from the official Discourse Blog. These topics are linked from the bottom of each blog entry where the blog comments would normally be.","description_excerpt":"Discussion topics generated from the official Discourse Blog. These topics are linked from the bottom of each blog entry where the blog comments would normally be.","topic_url":"/t/about-the-blog-category/5250","read_restricted":false,"permission":null,"parent_category_id":67,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"created","sort_ascending":false,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":10,"name":"documentation","color":"00A94F","text_color":"FFFFFF","slug":"documentation","topic_count":2,"post_count":2,"position":2,"description":"Documentation for how to use Discourse, install and configure sites, and troubleshoot common issues. Includes topics for tutorials, how-tos, general reference and troubleshooting. Topics need to be created in one of the subcategories, and may only be created by trust level 2 and up.","description_text":"Documentation for how to use Discourse, install and configure sites, and troubleshoot common issues. Includes topics for tutorials, how-tos, general reference and troubleshooting. Topics need to be created in one of the subcategories, and may only be created by trust level 2 and up.","description_excerpt":"Documentation for how to use Discourse, install and configure sites, and troubleshoot common issues. Includes topics for tutorials, how-tos, general reference and troubleshooting. Topics need to be created in one of the subcategories, and may only be created by trust level 2 and up.","topic_url":"/t/about-the-documentation-category/2629","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":true,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"top","subcategory_list_style":"boxes","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":true,"custom_fields":{"has_chat_enabled":null,"activity_pub_ready":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Article","activity_pub_publication_type":"full_topic","activity_pub_username":"documentation","activity_pub_enabled":true,"activity_pub_name":"Documentation"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":true,"activity_pub_ready":true,"activity_pub_actor":{"id":31769,"handle":"documentation@meta.discourse.org","name":"Documentation"},"activity_pub_username":"documentation","activity_pub_name":"Documentation","activity_pub_default_visibility":"public","activity_pub_publication_type":"full_topic","activity_pub_post_object_type":"Article","uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":53,"name":"admins","color":"F15D22","text_color":"FFFFFF","slug":"admins","topic_count":222,"post_count":1703,"position":3,"description":"Guides for Discourse admins and community managers with admin access.","description_text":"Guides for Discourse admins and community managers with admin access.","description_excerpt":"Guides for Discourse admins and community managers with admin access.","topic_url":"/t/about-the-admins-category/56207","read_restricted":false,"permission":null,"parent_category_id":10,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":true,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":["hosted-support","migrations"],"allowed_tag_groups":[],"allow_global_tags":true,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":125,"name":"moderators","color":"40d0e2","text_color":"FFFFFF","slug":"moderators","topic_count":16,"post_count":71,"position":4,"description":"Documentation for moderators and community managers using Discourse.","description_text":"Documentation for moderators and community managers using Discourse.","description_excerpt":"Documentation for moderators and community managers using Discourse.","topic_url":"/t/about-the-moderators-category/238588","read_restricted":false,"permission":null,"parent_category_id":10,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":126,"name":"users","color":"0088CC","text_color":"FFFFFF","slug":"users","topic_count":53,"post_count":184,"position":5,"description":"Documentation for all members of communities running Discourse.","description_text":"Documentation for all members of communities running Discourse.","description_excerpt":"Documentation for all members of communities running Discourse.","topic_url":"/t/about-the-users-category/238917","read_restricted":false,"permission":null,"parent_category_id":10,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":55,"name":"sysadmin","color":"E9DD00","text_color":"FFFFFF","slug":"sysadmin","topic_count":175,"post_count":2803,"position":6,"description":"Documentation for self-hosters and Discourse system administrators.","description_text":"Documentation for self-hosters and Discourse system administrators.","description_excerpt":"Documentation for self-hosters and Discourse system administrators.","topic_url":"/t/about-the-sysadmin-category/56209","read_restricted":false,"permission":null,"parent_category_id":10,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":true,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":["migrations"],"allowed_tag_groups":[],"allow_global_tags":true,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":127,"name":"theme developers","color":"92278F","text_color":"FFFFFF","slug":"theme-developers","topic_count":41,"post_count":215,"position":7,"description":"Documentation for developing themes and components that can be installed by admins.","description_text":"Documentation for developing themes and components that can be installed by admins.","description_excerpt":"Documentation for developing themes and components that can be installed by admins.","topic_url":"/t/about-the-theme-developers-category/239285","read_restricted":false,"permission":null,"parent_category_id":10,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":56,"name":"developers","color":"00A94F","text_color":"FFFFFF","slug":"devs","topic_count":96,"post_count":1185,"position":8,"description":"Documentation for developing features, plugins, or integrations with Discourse.","description_text":"Documentation for developing features, plugins, or integrations with Discourse.","description_excerpt":"Documentation for developing features, plugins, or integrations with Discourse.","topic_url":"/t/about-the-developers-category/56210","read_restricted":false,"permission":null,"parent_category_id":10,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":true,"custom_fields":{"has_chat_enabled":null,"activity_pub_ready":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Article","activity_pub_enabled":true,"activity_pub_username":"developer-docs","activity_pub_name":"Developer Documentation","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":true,"activity_pub_ready":true,"activity_pub_actor":{"id":35545,"handle":"developer-docs@meta.discourse.org","name":"Developer Documentation"},"activity_pub_username":"developer-docs","activity_pub_name":"Developer Documentation","activity_pub_default_visibility":"public","activity_pub_publication_type":"first_post","activity_pub_post_object_type":"Article","uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":6,"name":"support","color":"CEA9A9","text_color":"FFFFFF","slug":"support","topic_count":15841,"post_count":103687,"position":9,"description":"The category for general support questions on using your Discourse site.","description_text":"The category for general support questions on using your Discourse site.","description_excerpt":"The category for general support questions on using your Discourse site.","topic_url":"/t/about-the-support-category/389","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"\u003e Before asking, did you search first? Press 🔍 at the upper right to search.","has_children":true,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_accepted_answers":"true","enable_unassigned_filter":"false","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":21,"name":"wordpress","color":"F9CFCF","text_color":"FFFFFF","slug":"wordpress","topic_count":732,"post_count":5110,"position":10,"description":"Support for the official Discourse WordPress plugin at \u003ca href=\"https://github.com/discourse/wp-discourse\" class=\"inline-onebox\"\u003eGitHub - discourse/wp-discourse: WordPress plugin that lets you use Discourse as the community engine for a WordPress blog\u003c/a\u003e","description_text":"Support for the official Discourse WordPress plugin at GitHub - discourse/wp-discourse: WordPress plugin that lets you use Discourse as the community engine for a WordPress blog","description_excerpt":"Support for the official Discourse WordPress plugin at \u003ca href=\"https://github.com/discourse/wp-discourse\" class=\"inline-onebox\"\u003eGitHub - discourse/wp-discourse: WordPress plugin that lets you use Discourse as the community engine for a WordPress blog\u003c/a\u003e","topic_url":"/t/about-the-wordpress-category/12282","read_restricted":false,"permission":null,"parent_category_id":6,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":"true","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":1,"name":"bug","color":"e9dd00","text_color":"000000","slug":"bug","topic_count":5153,"post_count":35973,"position":11,"description":"A bug report means \u003cstrong\u003esomething is broken, preventing normal/typical use of Discourse\u003c/strong\u003e. Do be sure to search prior to submitting bugs. \u003ca href=\"https://meta.discourse.org/t/how-to-write-a-good-bug-report/183671/\"\u003eInclude repro steps\u003c/a\u003e, and only describe one bug per topic please.","description_text":"A bug report means something is broken, preventing normal/typical use of Discourse. Do be sure to search prior to submitting bugs. Include repro steps, and only describe one bug per topic please.","description_excerpt":"A bug report means something is broken, preventing normal/typical use of Discourse. Do be sure to search prior to submitting bugs. \u003ca href=\"https://meta.discourse.org/t/how-to-write-a-good-bug-report/183671/\"\u003eInclude repro steps\u003c/a\u003e, and only describe one bug per topic please.","topic_url":"/t/about-the-bug-category/2","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_accepted_answers":null,"enable_unassigned_filter":"true","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":true,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":9,"name":"ux","color":"5F497A","text_color":"FFFFFF","slug":"ux","topic_count":2700,"post_count":18225,"position":12,"description":"Discussion about the user interface of Discourse and how features are presented (including language and UI elements).","description_text":"Discussion about the user interface of Discourse and how features are presented (including language and UI elements).","description_excerpt":"Discussion about the user interface of Discourse and how features are presented (including language and UI elements).","topic_url":"/t/about-the-ux-category/2628","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":2,"name":"feature","color":"0E76BD","text_color":"FFFFFF","slug":"feature","topic_count":6997,"post_count":58067,"position":13,"description":"Discussion about existing Discourse features, how they can be improved or enhanced, and how proposed new features could work.","description_text":"Discussion about existing Discourse features, how they can be improved or enhanced, and how proposed new features could work.","description_excerpt":"Discussion about existing Discourse features, how they can be improved or enhanced, and how proposed new features could work.","topic_url":"/t/about-the-feature-category/11","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_ready":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_username":"feature","activity_pub_name":"Feature Requests","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post","activity_pub_enabled":true},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":true,"activity_pub_ready":true,"activity_pub_actor":{"id":1,"handle":"feature@meta.discourse.org","name":"Feature Requests"},"activity_pub_username":"feature","activity_pub_name":"Feature Requests","activity_pub_default_visibility":"public","activity_pub_publication_type":"first_post","activity_pub_post_object_type":"Note","uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":148,"name":"data \u0026 reporting","color":"D24899","text_color":"FFFFFF","slug":"data-reporting","topic_count":631,"post_count":3056,"position":14,"description":"This is the category for everything related to Discourse data and reporting. Here, you can discuss dashboard reports, custom badge queries, data-explorer queries and any other analytic add-ons.","description_text":"This is the category for everything related to Discourse data and reporting. Here, you can discuss dashboard reports, custom badge queries, data-explorer queries and any other analytic add-ons.","description_excerpt":"This is the category for everything related to Discourse data and reporting. Here, you can discuss dashboard reports, custom badge queries, data-explorer queries and any other analytic add-ons.","topic_url":"/t/about-the-data-reporting-category/274664","read_restricted":false,"permission":null,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","enable_accepted_answers":"true","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":24,"name":"sso","color":"d47711","text_color":"FFFFFF","slug":"sso","topic_count":493,"post_count":2546,"position":15,"description":"For queries specifically about SSO (single sign-on) and login using third-party providers (Google, Facebook, GitHub etc). See the \u003ca href=\"https://meta.discourse.org/t/discourseconnect-official-single-sign-on-for-discourse-sso/13045\"\u003eofficial documentation on DiscourseConnect SSO\u003c/a\u003e.","description_text":"For queries specifically about SSO (single sign-on) and login using third-party providers (Google, Facebook, GitHub etc). See the official documentation on DiscourseConnect SSO.","description_excerpt":"For queries specifically about SSO (single sign-on) and login using third-party providers (Google, Facebook, GitHub etc). See the \u003ca href=\"https://meta.discourse.org/t/discourseconnect-official-single-sign-on-for-discourse-sso/13045\"\u003eofficial documentation on DiscourseConnect SSO\u003c/a\u003e.","topic_url":"/t/about-the-sso-category/13110","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":"true","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":65,"name":"community","color":"12A89D","text_color":"FFFFFF","slug":"community","topic_count":866,"post_count":8844,"position":16,"description":"A great platform doesn’t guarantee success. Community building is a science. This category is for discussions about launching, building, growing and managing a thriving community.","description_text":"A great platform doesn’t guarantee success. Community building is a science. This category is for discussions about launching, building, growing and managing a thriving community.","description_excerpt":"A great platform doesn’t guarantee success. Community building is a science. This category is for discussions about launching, building, growing and managing a thriving community.","topic_url":"/t/about-the-community-category/67750","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":7,"name":"dev","color":"292929","text_color":"fff","slug":"dev","topic_count":3467,"post_count":20210,"position":17,"description":"The category for all things Discourse Development. Building a customization for yourself or the community? Then this is the category for you!","description_text":"The category for all things Discourse Development. Building a customization for yourself or the community? Then this is the category for you!","description_excerpt":"The category for all things Discourse Development. Building a customization for yourself or the community? Then this is the category for you!","topic_url":"/t/about-the-dev-category/1026","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":true,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"boxes","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":"true","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":27,"name":"translations","color":"808281","text_color":"FFFFFF","slug":"translations","topic_count":297,"post_count":1832,"position":18,"description":"This category is for discussion about localizing Discourse.","description_text":"This category is for discussion about localizing Discourse.","description_excerpt":"This category is for discussion about localizing Discourse.","topic_url":"/t/about-the-translations-category/14549","read_restricted":false,"permission":null,"parent_category_id":7,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":14,"name":"marketplace","color":"8C6238","text_color":"FFFFFF","slug":"marketplace","topic_count":1162,"post_count":5865,"position":19,"description":"This is your hub for all Discourse-related commerce: jobs, gigs, plugins, themes, hosting, and more.","description_text":"This is your hub for all Discourse-related commerce: jobs, gigs, plugins, themes, hosting, and more.","description_excerpt":"This is your hub for all Discourse-related commerce: jobs, gigs, plugins, themes, hosting, and more.","topic_url":"/t/about-the-marketplace-category/5425","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"What would you like done?\n\nWhen do you need it done?\n\nWhat is your budget, in $ USD that you can offer for this task?\n\n\u003c!-- We encourage caution and due diligence when engaging with potential contractors or clients. Verify their credentials, check previous work, and ensure a transparent and legitimate transaction. Always remember, your safety and security in the marketplace is your responsibility. --\u003e","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":["delivered"],"allowed_tag_groups":[],"allow_global_tags":true,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":22,"name":"plugin","color":"F7941D","text_color":"FFFFFF","slug":"plugin","topic_count":315,"post_count":10429,"position":20,"description":"A directory of Discourse plugins, both official and third-party.","description_text":"A directory of Discourse plugins, both official and third-party.","description_excerpt":"A directory of Discourse plugins, both official and third-party.","topic_url":"/t/about-the-plugin-category/12648","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"| | | |\n| - | - | - |\n| :information_source: | **Summary** | In a few words, what does this plugin do? |\n| :hammer_and_wrench: | **Repository Link** | \u003c\u003e |\n| :open_book: | **Install Guide** | [How to install plugins in Discourse](https://meta.discourse.org/t/install-plugins-in-discourse/19157) |\n\n\u003cbr\u003e \n\n### Features\n \nDescribe the major features of the plugin\n \n### Configuration\n \nInclude detailed steps on how to configure the plugin (include screenshots where necessary)\n \n### CHANGELOG\n- Add new bullets when major features are committed here\n \n### TODO\n- Add any #pr-welcome TODO tasks here","has_children":true,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"boxes","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":["Resource Status"],"allow_global_tags":true,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":5,"name":"extras","color":"25AAE2","text_color":"FFFFFF","slug":"extras","topic_count":90,"post_count":985,"position":21,"description":"A directory of all extensions \u0026amp; integrations for Discourse which are \u003cem\u003enot\u003c/em\u003e Discourse plugins, i.e. a CMS plugin, a browser extension or a native application.","description_text":"A directory of all extensions \u0026amp; integrations for Discourse which are not Discourse plugins, i.e. a CMS plugin, a browser extension or a native application.","description_excerpt":"A directory of all extensions \u0026amp; integrations for Discourse which are not Discourse plugins, i.e. a CMS plugin, a browser extension or a native application.","topic_url":"/t/about-the-extras-category/28","read_restricted":false,"permission":null,"parent_category_id":22,"notification_level":1,"topic_template":"","has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":["Resource Status"],"allow_global_tags":true,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":61,"name":"theme","color":"E43D30","text_color":"FFFFFF","slug":"theme","topic_count":66,"post_count":2138,"position":22,"description":"Themes are expansive customizations that change multiple elements of the style of your forum design, and often also include additional front-end features.","description_text":"Themes are expansive customizations that change multiple elements of the style of your forum design, and often also include additional front-end features.","description_excerpt":"Themes are expansive customizations that change multiple elements of the style of your forum design, and often also include additional front-end features.","topic_url":"/t/about-the-theme-category/60925","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"||||\n|-|-|-|\n| :information_source: | **Summary** | ADD SHORT SUMMARY \n| :eyeglasses:|**Preview**| PREVIEW_LINK |\n| :hammer_and_wrench:|**Repository**| REPOSITORY_LINK |\n| :question:|**Install Guide**|[How to install a theme or theme component](https://meta.discourse.org/t/how-do-i-install-a-theme-or-theme-component/63682)|\n| :open_book:|**New to Discourse Themes?**| [Beginner’s guide to using Discourse Themes](https://meta.discourse.org/t/beginners-guide-to-using-discourse-themes/91966)\n\n\u003c!-- Describe this theme in one or two sentences --\u003e\n\nShort description...\n\n\u003c!-- Add screenshots (if applicable) --\u003e\n\nScreenshots...\n\n\u003c!-- Add more details and explain the settings (if applicable) --\u003e\n\nDetailed description...\n","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"none","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":["color-palette"],"allowed_tag_groups":["Resource Status"],"allow_global_tags":true,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":120,"name":"theme-component","color":"1dedf8","text_color":"FFFFFF","slug":"theme-component","topic_count":300,"post_count":7516,"position":23,"description":"Theme components are customizations that change surface elements of your forum design, or add extra front-end features.","description_text":"Theme components are customizations that change surface elements of your forum design, or add extra front-end features.","description_excerpt":"Theme components are customizations that change surface elements of your forum design, or add extra front-end features.","topic_url":"/t/about-the-theme-component-category/232731","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"| | | |\n| - | - | - |\n| :information_source: | **Summary** | ADD SHORT SUMMARY |\n| :eyeglasses: |**Preview**| PREVIEW_LINK |\n| :hammer_and_wrench: | **Repository**| REPOSITORY_LINK |\n| :question: | **Install Guide** | [How to install a theme or theme component](https://meta.discourse.org/t/how-do-i-install-a-theme-or-theme-component/63682) |\n| :open_book: | **New to Discourse Themes?** | [Beginner’s guide to using Discourse Themes](https://meta.discourse.org/t/beginners-guide-to-using-discourse-themes/91966) |\n\n\u003c!-- Fill in \"repoName\" and \"repoURL\" for the automatic install button --\u003e\n\n[wrap=theme-install-button repoName=\"Component's name\" repoUrl=\"GitHub repository link\"]\nInstall this theme component\n[/wrap]\n\n\u003c!-- Describe this theme/component in one or two sentences --\u003e\n\nShort description...\n\n\u003c!-- Add screenshots (if applicable) --\u003e\n\nScreenshots...\n\n\u003c!-- Add more details and explain the settings (if applicable) --\u003e\n\nDetailed description...","has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"none","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":["Resource Status"],"allow_global_tags":true,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":31,"name":"installation","color":"997E7E","text_color":"FFFFFF","slug":"installation","topic_count":3426,"post_count":29611,"position":24,"description":"Getting Discourse up and running, keeping it going, upgrading, and any other general sysadmin maintenance.","description_text":"Getting Discourse up and running, keeping it going, upgrading, and any other general sysadmin maintenance.","description_excerpt":"Getting Discourse up and running, keeping it going, upgrading, and any other general sysadmin maintenance.","topic_url":"/t/about-the-installation-category/21019","read_restricted":false,"permission":null,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":"true","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":106,"name":"migration","color":"652D90","text_color":"FFFFFF","slug":"migration","topic_count":215,"post_count":1558,"position":25,"description":"You want to migrate your community to Discourse? Awesome! This is the category where you can ask questions, get help, and document your Discourse migration journey.","description_text":"You want to migrate your community to Discourse? Awesome! This is the category where you can ask questions, get help, and document your Discourse migration journey.","description_excerpt":"You want to migrate your community to Discourse? Awesome! This is the category where you can ask questions, get help, and document your Discourse migration journey.","topic_url":"/t/about-the-migration-category/196969","read_restricted":false,"permission":null,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":"true","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":["Migration"],"allow_global_tags":true,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":8,"name":"hosting","color":"00AEEF","text_color":"FFFFFF","slug":"hosting","topic_count":501,"post_count":4117,"position":26,"description":"Topics about hosting Discourse, either on your own servers, in the cloud, or with specific hosting services.","description_text":"Topics about hosting Discourse, either on your own servers, in the cloud, or with specific hosting services.","description_excerpt":"Topics about hosting Discourse, either on your own servers, in the cloud, or with specific hosting services.","topic_url":"/t/about-the-hosting-category/2626","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post","enable_accepted_answers":"true"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":30,"name":"releases","color":"BF1E2E","text_color":"FFFFFF","slug":"releases","topic_count":23,"post_count":104,"position":27,"description":"Outlining each official release of Discourse, and plans for future releases.","description_text":"Outlining each official release of Discourse, and plans for future releases.","description_excerpt":"Outlining each official release of Discourse, and plans for future releases.","topic_url":"/t/about-the-releases-category/20857","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"created","sort_ascending":false,"show_subcategory_list":true,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":3,"name":"site feedback","color":"888","text_color":"FFFFFF","slug":"site-feedback","topic_count":417,"post_count":3220,"position":28,"description":"Discussion about \u003ca href=\"http://meta.discourse.org\"\u003emeta.discourse.org\u003c/a\u003e itself - the organization of this forum, how it works, and how we can improve this site.","description_text":"Discussion about meta.discourse.org itself - the organization of this forum, how it works, and how we can improve this site.","description_excerpt":"Discussion about \u003ca href=\"http://meta.discourse.org\"\u003emeta.discourse.org\u003c/a\u003e itself - the organization of this forum, how it works, and how we can improve this site.","topic_url":"/t/about-the-site-feedback-category/24","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":true,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":152,"name":"theme feedback","color":"ED207B","text_color":"FFFFFF","slug":"theme-feedback","topic_count":11,"post_count":44,"position":29,"description":"This is the category to gather all the UX reports for \u003ca href=\"https://meta.discourse.org/t/we-have-a-new-default-theme-here-on-meta/284692\"\u003eour new theme on meta\u003c/a\u003e. It also uses the new Form Templates feature that I’ve been wanting to try out.","description_text":"This is the category to gather all the UX reports for our new theme on meta. It also uses the new Form Templates feature that I’ve been wanting to try out.","description_excerpt":"This is the category to gather all the UX reports for \u003ca href=\"https://meta.discourse.org/t/we-have-a-new-default-theme-here-on-meta/284692\"\u003eour new theme on meta\u003c/a\u003e. It also uses the new Form Templates feature that I’ve been wanting to try out.","topic_url":"/t/about-the-theme-feedback-category/284904","read_restricted":false,"permission":null,"parent_category_id":3,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[1],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":157,"name":"forum summaries","color":"72e9a7","text_color":"FFFFFF","slug":"forum-summaries","topic_count":4,"post_count":36,"position":30,"description":"Stay up-to-date with the pulse of our community through AI-crafted summaries. This category harnesses the power of artificial intelligence to collate and condense forum activities, providing you with comprehensive yet succinct overviews. From emerging discussions to trending topics, our AI summaries deliver the essence of the Discourse community’s heartbeat, right at your fingertips.","description_text":"Stay up-to-date with the pulse of our community through AI-crafted summaries. This category harnesses the power of artificial intelligence to collate and condense forum activities, providing you with comprehensive yet succinct overviews. From emerging discussions to trending topics, our AI summaries deliver the essence of the Discourse community’s heartbeat, right at your fingertips.","description_excerpt":"Stay up-to-date with the pulse of our community through AI-crafted summaries. This category harnesses the power of artificial intelligence to collate and condense forum activities, providing you with comprehensive yet succinct overviews. From emerging discussions to trending topics, our AI summarie\u0026hellip;","topic_url":"/t/about-the-forum-summaries-category/291765","read_restricted":false,"permission":null,"parent_category_id":3,"notification_level":0,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":35,"name":"praise","color":"9EB83B","text_color":"FFFFFF","slug":"praise","topic_count":294,"post_count":1091,"position":31,"description":"Have something nice to say about Discourse?","description_text":"Have something nice to say about Discourse?","description_excerpt":"Have something nice to say about Discourse?","topic_url":"/t/about-the-praise-category/30010","read_restricted":false,"permission":null,"notification_level":1,"topic_template":null,"has_children":true,"sort_order":null,"sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":63,"name":"comparison","color":"F1592A","text_color":"FFFFFF","slug":"comparison","topic_count":11,"post_count":137,"position":32,"description":"Topics comparing Discourse to other platforms.","description_text":"Topics comparing Discourse to other platforms.","description_excerpt":"Topics comparing Discourse to other platforms.","topic_url":"/t/about-the-comparison-category/65736","read_restricted":false,"permission":null,"parent_category_id":35,"notification_level":1,"topic_template":"### About PLATFORM_NAME\n\nA blurb about the platform being compared to Discourse. \n\nhttp://link.to.website\n\n ### Previous discussions:\n\nlinks to previous discussions related\n\n### Importer status\n\nIs there an official Discourse importer? Where is it? ","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":105,"name":"community support program","color":"92278F","text_color":"FFFFFF","slug":"support-program","topic_count":4,"post_count":27,"position":33,"description":"Get recognition for your work in helping and supporting Discourse communities by joining our Community Support Program.","description_text":"Get recognition for your work in helping and supporting Discourse communities by joining our Community Support Program.","description_excerpt":"Get recognition for your work in helping and supporting Discourse communities by joining our Community Support Program.","topic_url":"/t/about-the-community-support-program-category/193906","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":true,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":124,"name":"General","color":"25AAE2","text_color":"FFFFFF","slug":"general","topic_count":155,"post_count":1408,"position":35,"description":"Create topics here that don’t fit into any other existing category.","description_text":"Create topics here that don’t fit into any other existing category.","description_excerpt":"Create topics here that don’t fit into any other existing category.","topic_url":"/t/about-the-general-category/237517","read_restricted":false,"permission":null,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":17,"name":"Uncategorized","color":"AB9364","text_color":"FFFFFF","slug":"uncategorized","topic_count":0,"post_count":0,"position":77,"description":"Topics that don't need a category, or don't fit into any other existing category.","description_text":"Topics that don't need a category, or don't fit into any other existing category.","description_excerpt":"Topics that don't need a category, or don't fit into any other existing category.","topic_url":"/t/","read_restricted":false,"permission":null,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false}],"markdown_additional_options":{"chat":{"limited_pretty_text_features":["anchor","bbcode-block","bbcode-inline","code","category-hashtag","censored","chat-transcript","discourse-local-dates","emoji","emojiShortcuts","inlineEmoji","html-img","hashtag-autocomplete","mentions","unicodeUsernames","onebox","quotes","spoiler-alert","table","text-post-process","upload-protocol","watched-words"],"limited_pretty_text_markdown_rules":["autolink","list","backticks","newline","code","fence","image","table","linkify","link","strikethrough","blockquote","emphasis","replacements"],"hashtag_configurations":{"topic-composer":["category","tag","channel"],"chat-composer":["channel","category","tag"]}}},"hashtag_configurations":{"topic-composer":["category","tag","channel"],"chat-composer":["channel","category","tag"]},"hashtag_icons":{"category":"folder","tag":"tag","channel":"comment"},"displayed_about_plugin_stat_groups":["chat_messages"],"anonymous_default_navigation_menu_tags":[{"name":"official","description":"This is a theme or plugin built by the Discourse team","pm_only":false},{"name":"release-notes","description":"Release notes for Discourse beta and stable branches. See https://meta.discourse.org/t/198215 for more details on the Discourse release process.","pm_only":false}],"anonymous_sidebar_sections":[{"id":56,"title":"Community","links":[{"id":274,"name":"Topics","value":"/latest","icon":"layer-group","external":false,"full_reload":false,"segment":"primary"},{"id":275,"name":"My Posts","value":"/my/activity","icon":"user","external":false,"full_reload":true,"segment":"primary"},{"id":276,"name":"Review","value":"/review","icon":"flag","external":false,"full_reload":false,"segment":"primary"},{"id":277,"name":"Admin","value":"/admin","icon":"wrench","external":false,"full_reload":false,"segment":"primary"},{"id":279,"name":"Users","value":"/u","icon":"users","external":false,"full_reload":false,"segment":"secondary"},{"id":280,"name":"About","value":"/about","icon":"info-circle","external":false,"full_reload":false,"segment":"secondary"},{"id":281,"name":"FAQ","value":"/faq","icon":"question-circle","external":false,"full_reload":false,"segment":"secondary"},{"id":282,"name":"Groups","value":"/g","icon":"user-friends","external":false,"full_reload":false,"segment":"secondary"},{"id":283,"name":"Badges","value":"/badges","icon":"certificate","external":false,"full_reload":false,"segment":"secondary"},{"id":287,"name":"Leaderboard","value":"/leaderboard/7","icon":"trophy","external":false,"full_reload":false,"segment":"secondary"},{"id":290,"name":"Global Leaderboard","value":"/leaderboard","icon":"trophy","external":false,"full_reload":false,"segment":"secondary"},{"id":291,"name":"Topic Filter","value":"/filter","icon":"filter","external":false,"full_reload":false,"segment":"secondary"}],"slug":"community","public":true,"section_type":"community"}],"tos_url":"/tos","privacy_policy_url":"https://www.discourse.org/privacy","activity_pub_enabled":true,"activity_pub_publishing_enabled":true,"activity_pub_host":"meta.discourse.org","docs_path":"docs","default_gamification_leaderboard_id":1,"hosting_tier":"enterprise","archetypes":[{"id":"regular","name":"Regular Topic","options":[]},{"id":"banner","name":"Banner Topic","options":[]}],"user_fields":[{"id":2,"name":"Pronouns","description":"Gender Pronouns (he/him, she/her, they/them, etc.)","field_type":"text","editable":true,"required":false,"show_on_profile":true,"show_on_user_card":true,"searchable":false,"position":1}],"auth_providers":[{"name":"facebook","custom_url":null,"pretty_name_override":null,"title_override":null,"frame_width":580,"frame_height":400,"can_connect":true,"can_revoke":true,"icon":"fab-facebook"},{"name":"google_oauth2","custom_url":null,"pretty_name_override":null,"title_override":null,"frame_width":850,"frame_height":500,"can_connect":true,"can_revoke":true,"icon":null},{"name":"github","custom_url":null,"pretty_name_override":null,"title_override":null,"frame_width":null,"frame_height":null,"can_connect":true,"can_revoke":true,"icon":"fab-github"},{"name":"twitter","custom_url":null,"pretty_name_override":null,"title_override":null,"frame_width":null,"frame_height":null,"can_connect":true,"can_revoke":true,"icon":"fab-twitter"},{"name":"discord","custom_url":null,"pretty_name_override":null,"title_override":null,"frame_width":null,"frame_height":null,"can_connect":true,"can_revoke":true,"icon":"fab-discord"},{"name":"apple","custom_url":null,"pretty_name_override":null,"title_override":null,"frame_width":null,"frame_height":null,"can_connect":true,"can_revoke":true,"icon":"fab-apple"}]} \ No newline at end of file +{"default_archetype":"regular","notification_types":{"mentioned":1,"replied":2,"quoted":3,"edited":4,"liked":5,"private_message":6,"invited_to_private_message":7,"invitee_accepted":8,"posted":9,"moved_post":10,"linked":11,"granted_badge":12,"invited_to_topic":13,"custom":14,"group_mentioned":15,"group_message_summary":16,"watching_first_post":17,"topic_reminder":18,"liked_consolidated":19,"post_approved":20,"code_review_commit_approved":21,"membership_request_accepted":22,"membership_request_consolidated":23,"bookmark_reminder":24,"reaction":25,"votes_released":26,"event_reminder":27,"event_invitation":28,"chat_mention":29,"chat_message":30,"chat_invitation":31,"chat_group_mention":32,"chat_quoted":33,"assigned":34,"question_answer_user_commented":35,"watching_category_or_tag":36,"new_features":37,"admin_problems":38,"following":800,"following_created_topic":801,"following_replied":802,"circles_activity":900},"post_types":{"regular":1,"moderator_action":2,"small_action":3,"whisper":4},"user_tips":{"first_notification":1,"topic_timeline":2,"post_menu":3,"topic_notification_levels":4,"suggested_topics":5,"admin_guide":6},"trust_levels":{"newuser":0,"basic":1,"member":2,"regular":3,"leader":4},"groups":[{"id":183,"name":"ai-agents","flair_url":null,"flair_bg_color":"","flair_color":""},{"id":160,"name":"community-moderators","flair_url":null,"flair_bg_color":"","flair_color":""},{"id":184,"name":"glimmer-search-menu","flair_url":null,"flair_bg_color":"","flair_color":""},{"id":157,"name":"new-new-testers","flair_url":null,"flair_bg_color":"","flair_color":""},{"id":48,"name":"plugin_authors","flair_url":"fa-plug","flair_bg_color":"dddddd","flair_color":"111111"},{"id":148,"name":"support-advocates","flair_url":"https://d11a6trkgmumsb.cloudfront.net/original/3X/e/4/e4038d4d9848de2eabab38e17b8bdb69da154024.svg","flair_bg_color":"FFFFFF","flair_color":""},{"id":151,"name":"support-enthusiasts","flair_url":"https://d11a6trkgmumsb.cloudfront.net/original/3X/1/3/13f5d8d7e56be8a6a1ea3de009b985a548aec8d4.svg","flair_bg_color":"FFFFFF","flair_color":""},{"id":142,"name":"support-experts","flair_url":"https://d11a6trkgmumsb.cloudfront.net/original/3X/e/2/e250ec403580530d19e6a9ed42d0d525a51a9dbe.svg","flair_bg_color":"FFFFFF","flair_color":""},{"id":118,"name":"support-explorers","flair_url":"https://d11a6trkgmumsb.cloudfront.net/original/4X/d/1/b/d1ba0acf09b9d01f87f9e05bbee1dc5b0e316d5f.png","flair_bg_color":"dddddd","flair_color":"111111"},{"id":47,"name":"team","flair_url":"https://d11a6trkgmumsb.cloudfront.net/original/3X/e/b/ebee30bd98aef20357e4a177a5a1e45b877ce088.svg","flair_bg_color":"","flair_color":"111"},{"id":73,"name":"theme_authors","flair_url":"fa-paint-brush","flair_bg_color":"ddd","flair_color":"111"},{"id":84,"name":"theme_creator","flair_url":"fa-palette","flair_bg_color":"ddd","flair_color":"111"},{"id":50,"name":"translators","flair_url":"fa-globe","flair_bg_color":"ddd","flair_color":"111"}],"filters":["latest","unread","new","unseen","top","read","posted","bookmarks","hot"],"periods":["all","yearly","quarterly","monthly","weekly","daily"],"top_menu_items":["latest","unread","new","unseen","top","read","posted","bookmarks","hot","categories"],"anonymous_top_menu_items":["latest","top","categories","hot","categories","top"],"uncategorized_category_id":17,"user_field_max_length":2048,"post_action_types":[{"id":2,"name_key":"like","name":"Like","description":"Like this post","short_description":"Like this post","is_flag":false,"is_custom_flag":false},{"id":3,"name_key":"off_topic","name":"Off-Topic","description":"This post is not relevant to the current discussion as defined by the title and first post, and should probably be moved elsewhere.","short_description":"Not relevant to the discussion","is_flag":true,"is_custom_flag":false},{"id":4,"name_key":"inappropriate","name":"Inappropriate","description":"This post contains content that a reasonable person would consider offensive, abusive, to be hateful conduct or a violation of \u003ca href=\"/guidelines\"\u003eour community guidelines\u003c/a\u003e.","short_description":"A violation of \u003ca href=\"/guidelines\"\u003eour community guidelines\u003c/a\u003e","is_flag":true,"is_custom_flag":false},{"id":8,"name_key":"spam","name":"Spam","description":"This post is an advertisement, or vandalism. It is not useful or relevant to the current topic.","short_description":"This is an advertisement or vandalism","is_flag":true,"is_custom_flag":false},{"id":6,"name_key":"notify_user","name":"Send @%{username} a message","description":"I want to talk to this person directly and agentlly about their post.","short_description":"I want to talk to this person directly and agentlly about their post.","is_flag":true,"is_custom_flag":true},{"id":10,"name_key":"illegal","name":"Illegal","description":"This post requires staff attention because I believe it contains content that is illegal.","short_description":"This is illegal","is_flag":true,"is_custom_flag":true},{"id":null,"name_key":null,"name":"Translation missing: en.post_action_types..title","description":"Translation missing: en.post_action_types.description","short_description":"Translation missing: en.post_action_types.short_description","is_flag":false,"is_custom_flag":false},{"id":7,"name_key":"notify_moderators","name":"Something Else","description":"This post requires staff attention for another reason not listed above.","short_description":"Requires staff attention for another reason","is_flag":true,"is_custom_flag":true}],"topic_flag_types":[{"id":4,"name_key":"inappropriate","name":"Inappropriate","description":"This topic contains content that a reasonable person would consider offensive, abusive, to be hateful conduct or a violation of \u003ca href=\"/guidelines\"\u003eour community guidelines\u003c/a\u003e.","short_description":"A violation of \u003ca href=\"/guidelines\"\u003eour community guidelines\u003c/a\u003e","is_flag":true,"is_custom_flag":false},{"id":8,"name_key":"spam","name":"Spam","description":"This topic is an advertisement. It is not useful or relevant to this site, but promotional in nature.","short_description":"This is an advertisement","is_flag":true,"is_custom_flag":false},{"id":10,"name_key":"illegal","name":"Illegal","description":"This topic requires staff attention because I believe it contains content that is illegal.","short_description":"This is illegal","is_flag":true,"is_custom_flag":true},{"id":null,"name_key":null,"name":"Translation missing: en.topic_flag_types..title","description":"Translation missing: en.topic_flag_types.description","short_description":"Translation missing: en.topic_flag_types.short_description","is_flag":false,"is_custom_flag":false},{"id":7,"name_key":"notify_moderators","name":"Something Else","description":"This topic requires general staff attention based on the \u003ca href=\"/guidelines\"\u003eguidelines\u003c/a\u003e, \u003ca href=\"/tos\"\u003eTOS\u003c/a\u003e, or for another reason not listed above.","short_description":"Requires staff attention for another reason","is_flag":true,"is_custom_flag":true}],"can_create_tag":false,"can_tag_topics":false,"can_tag_pms":false,"tags_filter_regexp":"[/\\?#\\[\\]@!\\$\u0026'\\(\\)\\*\\+,;=\\.%\\\\`^\\s|\\{\\}\"\u003c\u003e]+","top_tags":["how-to","chat","rest-api","sql-query","unsupported-install","email","pr-welcome","completed","ai","activity-summary","official","solved","sidebar","release-notes","server-resources","calendar-and-event","badges","topic-voting","docker","tags","advertising","invites","search","sql-triggered-badge","subscriptions","new-feature","chat-integration","wordpress","groups","themes","css"],"navigation_menu_site_top_tags":[{"name":"how-to","description":"How to guides contain steps to follow to solve a specific problem","pm_only":false},{"name":"chat","description":null,"pm_only":false},{"name":"rest-api","description":"Topics about making an external request to Discourse","pm_only":false},{"name":"sql-query","description":"SQL queries for the data-explorer","pm_only":false},{"name":"unsupported-install","description":null,"pm_only":false}],"topic_featured_link_allowed_category_ids":[149,120,122,157,121,153,127,9,136,138,61,83,96,56,132,123,144,104,10,27,137,25,124,22,134,24,110,105,143,119,145,135,129,7,30,89,131,150,6,155,31,20,133,55,102,148,147,95,152,151,128,154,21,139,108,69,3,14,125,63,2,126,115,106,1,118,117,17,53,13,5,65,8,67,35],"user_themes":[{"theme_id":272,"name":"Accessible contrast (dark)","default":false,"color_scheme_id":85},{"theme_id":271,"name":"Accessible contrast (light)","default":false,"color_scheme_id":84},{"theme_id":242,"name":"Air Theme","default":false,"color_scheme_id":82},{"theme_id":337,"name":"Central","default":false,"color_scheme_id":100},{"theme_id":335,"name":"CentralStaffOnly","default":false,"color_scheme_id":96},{"theme_id":31,"name":"Dark","default":false,"color_scheme_id":86},{"theme_id":140,"name":"Default","default":false,"color_scheme_id":34},{"theme_id":281,"name":"Default (full-width)","default":false,"color_scheme_id":34},{"theme_id":51,"name":"Discourse-classic","default":false,"color_scheme_id":34},{"theme_id":296,"name":"Fully","default":false,"color_scheme_id":null},{"theme_id":131,"name":"Ghost","default":false,"color_scheme_id":66},{"theme_id":80,"name":"Graceful","default":false,"color_scheme_id":54},{"theme_id":83,"name":"Grey Amber","default":false,"color_scheme_id":57},{"theme_id":232,"name":"Hidden Whispers","default":false,"color_scheme_id":null},{"theme_id":34,"name":"Material Design","default":false,"color_scheme_id":37},{"theme_id":331,"name":"Meta Branded","default":true,"color_scheme_id":34},{"theme_id":125,"name":"Minima Dark","default":false,"color_scheme_id":43},{"theme_id":299,"name":"redditish","default":false,"color_scheme_id":null},{"theme_id":30,"name":"Sam's Simple Theme","default":false,"color_scheme_id":null}],"user_color_schemes":[{"id":93,"name":"Central Dark","is_dark":true},{"id":95,"name":"Central Dark","is_dark":true},{"id":100,"name":"Central Light","is_dark":false},{"id":96,"name":"Central Light","is_dark":false},{"id":86,"name":"Dark","is_dark":true},{"id":34,"name":"Default Light","is_dark":false},{"id":87,"name":"Solarized Light","is_dark":false},{"id":85,"name":"WCAG Dark","is_dark":true},{"id":84,"name":"WCAG Light","is_dark":false}],"default_dark_color_scheme":null,"censored_regexp":[{"(?:\\P{L}|^)(EICARTESTCENSOR)(?=\\P{L}|$)":{"case_sensitive":false}}],"custom_emoji_translation":{},"watched_words_replace":{"(?:\\P{L}|^)(¯_\\(ツ\\)_/¯)(?=\\P{L}|$)":{"word":"¯_(ツ)_/¯","replacement":"¯\\_(ツ)_/¯","case_sensitive":false}},"watched_words_link":{"(?:\\P{L}|^)(Data Explorer)(?=\\P{L}|$)":{"word":"Data Explorer","replacement":"https://meta.discourse.org/t/32566?silent=true","case_sensitive":false},"(?:\\P{L}|^)(discourseconnect)(?=\\P{L}|$)":{"word":"discourseconnect","replacement":"https://meta.discourse.org/t/13045?silent=true","case_sensitive":false},"(?:\\P{L}|^)(discourse connect)(?=\\P{L}|$)":{"word":"discourse connect","replacement":"https://meta.discourse.org/t/13045?silent=true","case_sensitive":false},"(?:\\P{L}|^)(discourse sso)(?=\\P{L}|$)":{"word":"discourse sso","replacement":"https://meta.discourse.org/t/13045?silent=true","case_sensitive":false},"(?:\\P{L}|^)(official install)(?=\\P{L}|$)":{"word":"official install","replacement":"https://meta.discourse.org/t/142537?silent=true","case_sensitive":false},"(?:\\P{L}|^)(standard install)(?=\\P{L}|$)":{"word":"standard install","replacement":"https://meta.discourse.org/t/142537?silent=true","case_sensitive":false},"(?:\\P{L}|^)(safe mode)(?=\\P{L}|$)":{"word":"safe mode","replacement":"https://meta.discourse.org/t/53504?silent=true","case_sensitive":false}},"categories":[{"id":67,"name":"announcements","color":"ED207B","text_color":"FFFFFF","slug":"announcements","topic_count":312,"post_count":3973,"position":0,"description":"The place for all Discourse announcements.","description_text":"The place for all Discourse announcements.","description_excerpt":"The place for all Discourse announcements.","topic_url":"/t/about-the-announcements-category/68629","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":true,"sort_order":null,"sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":true,"custom_fields":{"has_chat_enabled":null,"activity_pub_ready":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_enabled":true,"activity_pub_username":"announcements","activity_pub_name":"Announcements","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":["new-feature","security"],"allowed_tag_groups":[],"allow_global_tags":true,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":true,"activity_pub_ready":true,"activity_pub_actor":{"id":2,"handle":"announcements@meta.discourse.org","name":"Announcements"},"activity_pub_username":"announcements","activity_pub_name":"Announcements","activity_pub_default_visibility":"public","activity_pub_publication_type":"first_post","activity_pub_post_object_type":"Note","uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":13,"name":"blog","color":"8080ff","text_color":"FFFFFF","slug":"blog","topic_count":170,"post_count":1477,"position":1,"description":"Discussion topics generated from the official Discourse Blog. These topics are linked from the bottom of each blog entry where the blog comments would normally be.","description_text":"Discussion topics generated from the official Discourse Blog. These topics are linked from the bottom of each blog entry where the blog comments would normally be.","description_excerpt":"Discussion topics generated from the official Discourse Blog. These topics are linked from the bottom of each blog entry where the blog comments would normally be.","topic_url":"/t/about-the-blog-category/5250","read_restricted":false,"permission":null,"parent_category_id":67,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"created","sort_ascending":false,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":10,"name":"documentation","color":"00A94F","text_color":"FFFFFF","slug":"documentation","topic_count":2,"post_count":2,"position":2,"description":"Documentation for how to use Discourse, install and configure sites, and troubleshoot common issues. Includes topics for tutorials, how-tos, general reference and troubleshooting. Topics need to be created in one of the subcategories, and may only be created by trust level 2 and up.","description_text":"Documentation for how to use Discourse, install and configure sites, and troubleshoot common issues. Includes topics for tutorials, how-tos, general reference and troubleshooting. Topics need to be created in one of the subcategories, and may only be created by trust level 2 and up.","description_excerpt":"Documentation for how to use Discourse, install and configure sites, and troubleshoot common issues. Includes topics for tutorials, how-tos, general reference and troubleshooting. Topics need to be created in one of the subcategories, and may only be created by trust level 2 and up.","topic_url":"/t/about-the-documentation-category/2629","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":true,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"top","subcategory_list_style":"boxes","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":true,"custom_fields":{"has_chat_enabled":null,"activity_pub_ready":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Article","activity_pub_publication_type":"full_topic","activity_pub_username":"documentation","activity_pub_enabled":true,"activity_pub_name":"Documentation"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":true,"activity_pub_ready":true,"activity_pub_actor":{"id":31769,"handle":"documentation@meta.discourse.org","name":"Documentation"},"activity_pub_username":"documentation","activity_pub_name":"Documentation","activity_pub_default_visibility":"public","activity_pub_publication_type":"full_topic","activity_pub_post_object_type":"Article","uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":53,"name":"admins","color":"F15D22","text_color":"FFFFFF","slug":"admins","topic_count":222,"post_count":1703,"position":3,"description":"Guides for Discourse admins and community managers with admin access.","description_text":"Guides for Discourse admins and community managers with admin access.","description_excerpt":"Guides for Discourse admins and community managers with admin access.","topic_url":"/t/about-the-admins-category/56207","read_restricted":false,"permission":null,"parent_category_id":10,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":true,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":["hosted-support","migrations"],"allowed_tag_groups":[],"allow_global_tags":true,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":125,"name":"moderators","color":"40d0e2","text_color":"FFFFFF","slug":"moderators","topic_count":16,"post_count":71,"position":4,"description":"Documentation for moderators and community managers using Discourse.","description_text":"Documentation for moderators and community managers using Discourse.","description_excerpt":"Documentation for moderators and community managers using Discourse.","topic_url":"/t/about-the-moderators-category/238588","read_restricted":false,"permission":null,"parent_category_id":10,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":126,"name":"users","color":"0088CC","text_color":"FFFFFF","slug":"users","topic_count":53,"post_count":184,"position":5,"description":"Documentation for all members of communities running Discourse.","description_text":"Documentation for all members of communities running Discourse.","description_excerpt":"Documentation for all members of communities running Discourse.","topic_url":"/t/about-the-users-category/238917","read_restricted":false,"permission":null,"parent_category_id":10,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":55,"name":"sysadmin","color":"E9DD00","text_color":"FFFFFF","slug":"sysadmin","topic_count":175,"post_count":2803,"position":6,"description":"Documentation for self-hosters and Discourse system administrators.","description_text":"Documentation for self-hosters and Discourse system administrators.","description_excerpt":"Documentation for self-hosters and Discourse system administrators.","topic_url":"/t/about-the-sysadmin-category/56209","read_restricted":false,"permission":null,"parent_category_id":10,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":true,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":["migrations"],"allowed_tag_groups":[],"allow_global_tags":true,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":127,"name":"theme developers","color":"92278F","text_color":"FFFFFF","slug":"theme-developers","topic_count":41,"post_count":215,"position":7,"description":"Documentation for developing themes and components that can be installed by admins.","description_text":"Documentation for developing themes and components that can be installed by admins.","description_excerpt":"Documentation for developing themes and components that can be installed by admins.","topic_url":"/t/about-the-theme-developers-category/239285","read_restricted":false,"permission":null,"parent_category_id":10,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":56,"name":"developers","color":"00A94F","text_color":"FFFFFF","slug":"devs","topic_count":96,"post_count":1185,"position":8,"description":"Documentation for developing features, plugins, or integrations with Discourse.","description_text":"Documentation for developing features, plugins, or integrations with Discourse.","description_excerpt":"Documentation for developing features, plugins, or integrations with Discourse.","topic_url":"/t/about-the-developers-category/56210","read_restricted":false,"permission":null,"parent_category_id":10,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":true,"custom_fields":{"has_chat_enabled":null,"activity_pub_ready":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Article","activity_pub_enabled":true,"activity_pub_username":"developer-docs","activity_pub_name":"Developer Documentation","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":true,"activity_pub_ready":true,"activity_pub_actor":{"id":35545,"handle":"developer-docs@meta.discourse.org","name":"Developer Documentation"},"activity_pub_username":"developer-docs","activity_pub_name":"Developer Documentation","activity_pub_default_visibility":"public","activity_pub_publication_type":"first_post","activity_pub_post_object_type":"Article","uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":6,"name":"support","color":"CEA9A9","text_color":"FFFFFF","slug":"support","topic_count":15841,"post_count":103687,"position":9,"description":"The category for general support questions on using your Discourse site.","description_text":"The category for general support questions on using your Discourse site.","description_excerpt":"The category for general support questions on using your Discourse site.","topic_url":"/t/about-the-support-category/389","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"\u003e Before asking, did you search first? Press 🔍 at the upper right to search.","has_children":true,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_accepted_answers":"true","enable_unassigned_filter":"false","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":21,"name":"wordpress","color":"F9CFCF","text_color":"FFFFFF","slug":"wordpress","topic_count":732,"post_count":5110,"position":10,"description":"Support for the official Discourse WordPress plugin at \u003ca href=\"https://github.com/discourse/wp-discourse\" class=\"inline-onebox\"\u003eGitHub - discourse/wp-discourse: WordPress plugin that lets you use Discourse as the community engine for a WordPress blog\u003c/a\u003e","description_text":"Support for the official Discourse WordPress plugin at GitHub - discourse/wp-discourse: WordPress plugin that lets you use Discourse as the community engine for a WordPress blog","description_excerpt":"Support for the official Discourse WordPress plugin at \u003ca href=\"https://github.com/discourse/wp-discourse\" class=\"inline-onebox\"\u003eGitHub - discourse/wp-discourse: WordPress plugin that lets you use Discourse as the community engine for a WordPress blog\u003c/a\u003e","topic_url":"/t/about-the-wordpress-category/12282","read_restricted":false,"permission":null,"parent_category_id":6,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":"true","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":1,"name":"bug","color":"e9dd00","text_color":"000000","slug":"bug","topic_count":5153,"post_count":35973,"position":11,"description":"A bug report means \u003cstrong\u003esomething is broken, preventing normal/typical use of Discourse\u003c/strong\u003e. Do be sure to search prior to submitting bugs. \u003ca href=\"https://meta.discourse.org/t/how-to-write-a-good-bug-report/183671/\"\u003eInclude repro steps\u003c/a\u003e, and only describe one bug per topic please.","description_text":"A bug report means something is broken, preventing normal/typical use of Discourse. Do be sure to search prior to submitting bugs. Include repro steps, and only describe one bug per topic please.","description_excerpt":"A bug report means something is broken, preventing normal/typical use of Discourse. Do be sure to search prior to submitting bugs. \u003ca href=\"https://meta.discourse.org/t/how-to-write-a-good-bug-report/183671/\"\u003eInclude repro steps\u003c/a\u003e, and only describe one bug per topic please.","topic_url":"/t/about-the-bug-category/2","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_accepted_answers":null,"enable_unassigned_filter":"true","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":true,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":9,"name":"ux","color":"5F497A","text_color":"FFFFFF","slug":"ux","topic_count":2700,"post_count":18225,"position":12,"description":"Discussion about the user interface of Discourse and how features are presented (including language and UI elements).","description_text":"Discussion about the user interface of Discourse and how features are presented (including language and UI elements).","description_excerpt":"Discussion about the user interface of Discourse and how features are presented (including language and UI elements).","topic_url":"/t/about-the-ux-category/2628","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":2,"name":"feature","color":"0E76BD","text_color":"FFFFFF","slug":"feature","topic_count":6997,"post_count":58067,"position":13,"description":"Discussion about existing Discourse features, how they can be improved or enhanced, and how proposed new features could work.","description_text":"Discussion about existing Discourse features, how they can be improved or enhanced, and how proposed new features could work.","description_excerpt":"Discussion about existing Discourse features, how they can be improved or enhanced, and how proposed new features could work.","topic_url":"/t/about-the-feature-category/11","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_ready":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_username":"feature","activity_pub_name":"Feature Requests","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post","activity_pub_enabled":true},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":true,"activity_pub_ready":true,"activity_pub_actor":{"id":1,"handle":"feature@meta.discourse.org","name":"Feature Requests"},"activity_pub_username":"feature","activity_pub_name":"Feature Requests","activity_pub_default_visibility":"public","activity_pub_publication_type":"first_post","activity_pub_post_object_type":"Note","uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":148,"name":"data \u0026 reporting","color":"D24899","text_color":"FFFFFF","slug":"data-reporting","topic_count":631,"post_count":3056,"position":14,"description":"This is the category for everything related to Discourse data and reporting. Here, you can discuss dashboard reports, custom badge queries, data-explorer queries and any other analytic add-ons.","description_text":"This is the category for everything related to Discourse data and reporting. Here, you can discuss dashboard reports, custom badge queries, data-explorer queries and any other analytic add-ons.","description_excerpt":"This is the category for everything related to Discourse data and reporting. Here, you can discuss dashboard reports, custom badge queries, data-explorer queries and any other analytic add-ons.","topic_url":"/t/about-the-data-reporting-category/274664","read_restricted":false,"permission":null,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","enable_accepted_answers":"true","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":24,"name":"sso","color":"d47711","text_color":"FFFFFF","slug":"sso","topic_count":493,"post_count":2546,"position":15,"description":"For queries specifically about SSO (single sign-on) and login using third-party providers (Google, Facebook, GitHub etc). See the \u003ca href=\"https://meta.discourse.org/t/discourseconnect-official-single-sign-on-for-discourse-sso/13045\"\u003eofficial documentation on DiscourseConnect SSO\u003c/a\u003e.","description_text":"For queries specifically about SSO (single sign-on) and login using third-party providers (Google, Facebook, GitHub etc). See the official documentation on DiscourseConnect SSO.","description_excerpt":"For queries specifically about SSO (single sign-on) and login using third-party providers (Google, Facebook, GitHub etc). See the \u003ca href=\"https://meta.discourse.org/t/discourseconnect-official-single-sign-on-for-discourse-sso/13045\"\u003eofficial documentation on DiscourseConnect SSO\u003c/a\u003e.","topic_url":"/t/about-the-sso-category/13110","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":"true","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":65,"name":"community","color":"12A89D","text_color":"FFFFFF","slug":"community","topic_count":866,"post_count":8844,"position":16,"description":"A great platform doesn’t guarantee success. Community building is a science. This category is for discussions about launching, building, growing and managing a thriving community.","description_text":"A great platform doesn’t guarantee success. Community building is a science. This category is for discussions about launching, building, growing and managing a thriving community.","description_excerpt":"A great platform doesn’t guarantee success. Community building is a science. This category is for discussions about launching, building, growing and managing a thriving community.","topic_url":"/t/about-the-community-category/67750","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":7,"name":"dev","color":"292929","text_color":"fff","slug":"dev","topic_count":3467,"post_count":20210,"position":17,"description":"The category for all things Discourse Development. Building a customization for yourself or the community? Then this is the category for you!","description_text":"The category for all things Discourse Development. Building a customization for yourself or the community? Then this is the category for you!","description_excerpt":"The category for all things Discourse Development. Building a customization for yourself or the community? Then this is the category for you!","topic_url":"/t/about-the-dev-category/1026","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":true,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"boxes","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":"true","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":27,"name":"translations","color":"808281","text_color":"FFFFFF","slug":"translations","topic_count":297,"post_count":1832,"position":18,"description":"This category is for discussion about localizing Discourse.","description_text":"This category is for discussion about localizing Discourse.","description_excerpt":"This category is for discussion about localizing Discourse.","topic_url":"/t/about-the-translations-category/14549","read_restricted":false,"permission":null,"parent_category_id":7,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":14,"name":"marketplace","color":"8C6238","text_color":"FFFFFF","slug":"marketplace","topic_count":1162,"post_count":5865,"position":19,"description":"This is your hub for all Discourse-related commerce: jobs, gigs, plugins, themes, hosting, and more.","description_text":"This is your hub for all Discourse-related commerce: jobs, gigs, plugins, themes, hosting, and more.","description_excerpt":"This is your hub for all Discourse-related commerce: jobs, gigs, plugins, themes, hosting, and more.","topic_url":"/t/about-the-marketplace-category/5425","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"What would you like done?\n\nWhen do you need it done?\n\nWhat is your budget, in $ USD that you can offer for this task?\n\n\u003c!-- We encourage caution and due diligence when engaging with potential contractors or clients. Verify their credentials, check previous work, and ensure a transparent and legitimate transaction. Always remember, your safety and security in the marketplace is your responsibility. --\u003e","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":["delivered"],"allowed_tag_groups":[],"allow_global_tags":true,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":22,"name":"plugin","color":"F7941D","text_color":"FFFFFF","slug":"plugin","topic_count":315,"post_count":10429,"position":20,"description":"A directory of Discourse plugins, both official and third-party.","description_text":"A directory of Discourse plugins, both official and third-party.","description_excerpt":"A directory of Discourse plugins, both official and third-party.","topic_url":"/t/about-the-plugin-category/12648","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"| | | |\n| - | - | - |\n| :information_source: | **Summary** | In a few words, what does this plugin do? |\n| :hammer_and_wrench: | **Repository Link** | \u003c\u003e |\n| :open_book: | **Install Guide** | [How to install plugins in Discourse](https://meta.discourse.org/t/install-plugins-in-discourse/19157) |\n\n\u003cbr\u003e \n\n### Features\n \nDescribe the major features of the plugin\n \n### Configuration\n \nInclude detailed steps on how to configure the plugin (include screenshots where necessary)\n \n### CHANGELOG\n- Add new bullets when major features are committed here\n \n### TODO\n- Add any #pr-welcome TODO tasks here","has_children":true,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"boxes","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":["Resource Status"],"allow_global_tags":true,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":5,"name":"extras","color":"25AAE2","text_color":"FFFFFF","slug":"extras","topic_count":90,"post_count":985,"position":21,"description":"A directory of all extensions \u0026amp; integrations for Discourse which are \u003cem\u003enot\u003c/em\u003e Discourse plugins, i.e. a CMS plugin, a browser extension or a native application.","description_text":"A directory of all extensions \u0026amp; integrations for Discourse which are not Discourse plugins, i.e. a CMS plugin, a browser extension or a native application.","description_excerpt":"A directory of all extensions \u0026amp; integrations for Discourse which are not Discourse plugins, i.e. a CMS plugin, a browser extension or a native application.","topic_url":"/t/about-the-extras-category/28","read_restricted":false,"permission":null,"parent_category_id":22,"notification_level":1,"topic_template":"","has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":["Resource Status"],"allow_global_tags":true,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":61,"name":"theme","color":"E43D30","text_color":"FFFFFF","slug":"theme","topic_count":66,"post_count":2138,"position":22,"description":"Themes are expansive customizations that change multiple elements of the style of your forum design, and often also include additional front-end features.","description_text":"Themes are expansive customizations that change multiple elements of the style of your forum design, and often also include additional front-end features.","description_excerpt":"Themes are expansive customizations that change multiple elements of the style of your forum design, and often also include additional front-end features.","topic_url":"/t/about-the-theme-category/60925","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"||||\n|-|-|-|\n| :information_source: | **Summary** | ADD SHORT SUMMARY \n| :eyeglasses:|**Preview**| PREVIEW_LINK |\n| :hammer_and_wrench:|**Repository**| REPOSITORY_LINK |\n| :question:|**Install Guide**|[How to install a theme or theme component](https://meta.discourse.org/t/how-do-i-install-a-theme-or-theme-component/63682)|\n| :open_book:|**New to Discourse Themes?**| [Beginner’s guide to using Discourse Themes](https://meta.discourse.org/t/beginners-guide-to-using-discourse-themes/91966)\n\n\u003c!-- Describe this theme in one or two sentences --\u003e\n\nShort description...\n\n\u003c!-- Add screenshots (if applicable) --\u003e\n\nScreenshots...\n\n\u003c!-- Add more details and explain the settings (if applicable) --\u003e\n\nDetailed description...\n","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"none","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":["color-palette"],"allowed_tag_groups":["Resource Status"],"allow_global_tags":true,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":120,"name":"theme-component","color":"1dedf8","text_color":"FFFFFF","slug":"theme-component","topic_count":300,"post_count":7516,"position":23,"description":"Theme components are customizations that change surface elements of your forum design, or add extra front-end features.","description_text":"Theme components are customizations that change surface elements of your forum design, or add extra front-end features.","description_excerpt":"Theme components are customizations that change surface elements of your forum design, or add extra front-end features.","topic_url":"/t/about-the-theme-component-category/232731","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"| | | |\n| - | - | - |\n| :information_source: | **Summary** | ADD SHORT SUMMARY |\n| :eyeglasses: |**Preview**| PREVIEW_LINK |\n| :hammer_and_wrench: | **Repository**| REPOSITORY_LINK |\n| :question: | **Install Guide** | [How to install a theme or theme component](https://meta.discourse.org/t/how-do-i-install-a-theme-or-theme-component/63682) |\n| :open_book: | **New to Discourse Themes?** | [Beginner’s guide to using Discourse Themes](https://meta.discourse.org/t/beginners-guide-to-using-discourse-themes/91966) |\n\n\u003c!-- Fill in \"repoName\" and \"repoURL\" for the automatic install button --\u003e\n\n[wrap=theme-install-button repoName=\"Component's name\" repoUrl=\"GitHub repository link\"]\nInstall this theme component\n[/wrap]\n\n\u003c!-- Describe this theme/component in one or two sentences --\u003e\n\nShort description...\n\n\u003c!-- Add screenshots (if applicable) --\u003e\n\nScreenshots...\n\n\u003c!-- Add more details and explain the settings (if applicable) --\u003e\n\nDetailed description...","has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"none","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":["Resource Status"],"allow_global_tags":true,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":31,"name":"installation","color":"997E7E","text_color":"FFFFFF","slug":"installation","topic_count":3426,"post_count":29611,"position":24,"description":"Getting Discourse up and running, keeping it going, upgrading, and any other general sysadmin maintenance.","description_text":"Getting Discourse up and running, keeping it going, upgrading, and any other general sysadmin maintenance.","description_excerpt":"Getting Discourse up and running, keeping it going, upgrading, and any other general sysadmin maintenance.","topic_url":"/t/about-the-installation-category/21019","read_restricted":false,"permission":null,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":"true","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":106,"name":"migration","color":"652D90","text_color":"FFFFFF","slug":"migration","topic_count":215,"post_count":1558,"position":25,"description":"You want to migrate your community to Discourse? Awesome! This is the category where you can ask questions, get help, and document your Discourse migration journey.","description_text":"You want to migrate your community to Discourse? Awesome! This is the category where you can ask questions, get help, and document your Discourse migration journey.","description_excerpt":"You want to migrate your community to Discourse? Awesome! This is the category where you can ask questions, get help, and document your Discourse migration journey.","topic_url":"/t/about-the-migration-category/196969","read_restricted":false,"permission":null,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":"true","activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":["Migration"],"allow_global_tags":true,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":8,"name":"hosting","color":"00AEEF","text_color":"FFFFFF","slug":"hosting","topic_count":501,"post_count":4117,"position":26,"description":"Topics about hosting Discourse, either on your own servers, in the cloud, or with specific hosting services.","description_text":"Topics about hosting Discourse, either on your own servers, in the cloud, or with specific hosting services.","description_excerpt":"Topics about hosting Discourse, either on your own servers, in the cloud, or with specific hosting services.","topic_url":"/t/about-the-hosting-category/2626","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post","enable_accepted_answers":"true"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":30,"name":"releases","color":"BF1E2E","text_color":"FFFFFF","slug":"releases","topic_count":23,"post_count":104,"position":27,"description":"Outlining each official release of Discourse, and plans for future releases.","description_text":"Outlining each official release of Discourse, and plans for future releases.","description_excerpt":"Outlining each official release of Discourse, and plans for future releases.","topic_url":"/t/about-the-releases-category/20857","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"created","sort_ascending":false,"show_subcategory_list":true,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":3,"name":"site feedback","color":"888","text_color":"FFFFFF","slug":"site-feedback","topic_count":417,"post_count":3220,"position":28,"description":"Discussion about \u003ca href=\"http://meta.discourse.org\"\u003emeta.discourse.org\u003c/a\u003e itself - the organization of this forum, how it works, and how we can improve this site.","description_text":"Discussion about meta.discourse.org itself - the organization of this forum, how it works, and how we can improve this site.","description_excerpt":"Discussion about \u003ca href=\"http://meta.discourse.org\"\u003emeta.discourse.org\u003c/a\u003e itself - the organization of this forum, how it works, and how we can improve this site.","topic_url":"/t/about-the-site-feedback-category/24","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":true,"sort_order":"","sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":"","subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":152,"name":"theme feedback","color":"ED207B","text_color":"FFFFFF","slug":"theme-feedback","topic_count":11,"post_count":44,"position":29,"description":"This is the category to gather all the UX reports for \u003ca href=\"https://meta.discourse.org/t/we-have-a-new-default-theme-here-on-meta/284692\"\u003eour new theme on meta\u003c/a\u003e. It also uses the new Form Templates feature that I’ve been wanting to try out.","description_text":"This is the category to gather all the UX reports for our new theme on meta. It also uses the new Form Templates feature that I’ve been wanting to try out.","description_excerpt":"This is the category to gather all the UX reports for \u003ca href=\"https://meta.discourse.org/t/we-have-a-new-default-theme-here-on-meta/284692\"\u003eour new theme on meta\u003c/a\u003e. It also uses the new Form Templates feature that I’ve been wanting to try out.","topic_url":"/t/about-the-theme-feedback-category/284904","read_restricted":false,"permission":null,"parent_category_id":3,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[1],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":157,"name":"forum summaries","color":"72e9a7","text_color":"FFFFFF","slug":"forum-summaries","topic_count":4,"post_count":36,"position":30,"description":"Stay up-to-date with the pulse of our community through AI-crafted summaries. This category harnesses the power of artificial intelligence to collate and condense forum activities, providing you with comprehensive yet succinct overviews. From emerging discussions to trending topics, our AI summaries deliver the essence of the Discourse community’s heartbeat, right at your fingertips.","description_text":"Stay up-to-date with the pulse of our community through AI-crafted summaries. This category harnesses the power of artificial intelligence to collate and condense forum activities, providing you with comprehensive yet succinct overviews. From emerging discussions to trending topics, our AI summaries deliver the essence of the Discourse community’s heartbeat, right at your fingertips.","description_excerpt":"Stay up-to-date with the pulse of our community through AI-crafted summaries. This category harnesses the power of artificial intelligence to collate and condense forum activities, providing you with comprehensive yet succinct overviews. From emerging discussions to trending topics, our AI summarie\u0026hellip;","topic_url":"/t/about-the-forum-summaries-category/291765","read_restricted":false,"permission":null,"parent_category_id":3,"notification_level":0,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":35,"name":"praise","color":"9EB83B","text_color":"FFFFFF","slug":"praise","topic_count":294,"post_count":1091,"position":31,"description":"Have something nice to say about Discourse?","description_text":"Have something nice to say about Discourse?","description_excerpt":"Have something nice to say about Discourse?","topic_url":"/t/about-the-praise-category/30010","read_restricted":false,"permission":null,"notification_level":1,"topic_template":null,"has_children":true,"sort_order":null,"sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":63,"name":"comparison","color":"F1592A","text_color":"FFFFFF","slug":"comparison","topic_count":11,"post_count":137,"position":32,"description":"Topics comparing Discourse to other platforms.","description_text":"Topics comparing Discourse to other platforms.","description_excerpt":"Topics comparing Discourse to other platforms.","topic_url":"/t/about-the-comparison-category/65736","read_restricted":false,"permission":null,"parent_category_id":35,"notification_level":1,"topic_template":"### About PLATFORM_NAME\n\nA blurb about the platform being compared to Discourse. \n\nhttp://link.to.website\n\n ### Previous discussions:\n\nlinks to previous discussions related\n\n### Importer status\n\nIs there an official Discourse importer? Where is it? ","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"latest","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":105,"name":"community support program","color":"92278F","text_color":"FFFFFF","slug":"support-program","topic_count":4,"post_count":27,"position":33,"description":"Get recognition for your work in helping and supporting Discourse communities by joining our Community Support Program.","description_text":"Get recognition for your work in helping and supporting Discourse communities by joining our Community Support Program.","description_excerpt":"Get recognition for your work in helping and supporting Discourse communities by joining our Community Support Program.","topic_url":"/t/about-the-community-support-program-category/193906","read_restricted":false,"permission":null,"notification_level":1,"topic_template":"","has_children":false,"sort_order":"","sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":"","subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":true,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":"","form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":124,"name":"General","color":"25AAE2","text_color":"FFFFFF","slug":"general","topic_count":155,"post_count":1408,"position":35,"description":"Create topics here that don’t fit into any other existing category.","description_text":"Create topics here that don’t fit into any other existing category.","description_excerpt":"Create topics here that don’t fit into any other existing category.","topic_url":"/t/about-the-general-category/237517","read_restricted":false,"permission":null,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":false,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows_with_featured_topics","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"enable_unassigned_filter":null,"enable_accepted_answers":null,"activity_pub_default_visibility":"public","activity_pub_post_object_type":"Note","activity_pub_publication_type":"first_post"},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false},{"id":17,"name":"Uncategorized","color":"AB9364","text_color":"FFFFFF","slug":"uncategorized","topic_count":0,"post_count":0,"position":77,"description":"Topics that don't need a category, or don't fit into any other existing category.","description_text":"Topics that don't need a category, or don't fit into any other existing category.","description_excerpt":"Topics that don't need a category, or don't fit into any other existing category.","topic_url":"/t/","read_restricted":false,"permission":null,"notification_level":1,"topic_template":null,"has_children":false,"sort_order":null,"sort_ascending":null,"show_subcategory_list":true,"num_featured_topics":3,"default_view":null,"subcategory_list_style":"rows","default_top_period":"all","default_list_filter":"all","minimum_required_tags":0,"navigate_to_first_post_after_read":false,"custom_fields":{"has_chat_enabled":null,"activity_pub_enabled":null,"activity_pub_ready":null,"activity_pub_username":null,"activity_pub_name":null,"activity_pub_default_visibility":null,"activity_pub_publication_type":null,"activity_pub_post_object_type":null,"enable_unassigned_filter":null,"enable_accepted_answers":null},"allowed_tags":[],"allowed_tag_groups":[],"allow_global_tags":false,"read_only_banner":null,"form_template_ids":[],"activity_pub_enabled":false,"uploaded_logo":null,"uploaded_logo_dark":null,"uploaded_background":null,"uploaded_background_dark":null,"required_tag_groups":[],"can_edit":false}],"markdown_additional_options":{"chat":{"limited_pretty_text_features":["anchor","bbcode-block","bbcode-inline","code","category-hashtag","censored","chat-transcript","discourse-local-dates","emoji","emojiShortcuts","inlineEmoji","html-img","hashtag-autocomplete","mentions","unicodeUsernames","onebox","quotes","spoiler-alert","table","text-post-process","upload-protocol","watched-words"],"limited_pretty_text_markdown_rules":["autolink","list","backticks","newline","code","fence","image","table","linkify","link","strikethrough","blockquote","emphasis","replacements"],"hashtag_configurations":{"topic-composer":["category","tag","channel"],"chat-composer":["channel","category","tag"]}}},"hashtag_configurations":{"topic-composer":["category","tag","channel"],"chat-composer":["channel","category","tag"]},"hashtag_icons":{"category":"folder","tag":"tag","channel":"comment"},"displayed_about_plugin_stat_groups":["chat_messages"],"anonymous_default_navigation_menu_tags":[{"name":"official","description":"This is a theme or plugin built by the Discourse team","pm_only":false},{"name":"release-notes","description":"Release notes for Discourse beta and stable branches. See https://meta.discourse.org/t/198215 for more details on the Discourse release process.","pm_only":false}],"anonymous_sidebar_sections":[{"id":56,"title":"Community","links":[{"id":274,"name":"Topics","value":"/latest","icon":"layer-group","external":false,"full_reload":false,"segment":"primary"},{"id":275,"name":"My Posts","value":"/my/activity","icon":"user","external":false,"full_reload":true,"segment":"primary"},{"id":276,"name":"Review","value":"/review","icon":"flag","external":false,"full_reload":false,"segment":"primary"},{"id":277,"name":"Admin","value":"/admin","icon":"wrench","external":false,"full_reload":false,"segment":"primary"},{"id":279,"name":"Users","value":"/u","icon":"users","external":false,"full_reload":false,"segment":"secondary"},{"id":280,"name":"About","value":"/about","icon":"info-circle","external":false,"full_reload":false,"segment":"secondary"},{"id":281,"name":"FAQ","value":"/faq","icon":"question-circle","external":false,"full_reload":false,"segment":"secondary"},{"id":282,"name":"Groups","value":"/g","icon":"user-friends","external":false,"full_reload":false,"segment":"secondary"},{"id":283,"name":"Badges","value":"/badges","icon":"certificate","external":false,"full_reload":false,"segment":"secondary"},{"id":287,"name":"Leaderboard","value":"/leaderboard/7","icon":"trophy","external":false,"full_reload":false,"segment":"secondary"},{"id":290,"name":"Global Leaderboard","value":"/leaderboard","icon":"trophy","external":false,"full_reload":false,"segment":"secondary"},{"id":291,"name":"Topic Filter","value":"/filter","icon":"filter","external":false,"full_reload":false,"segment":"secondary"}],"slug":"community","public":true,"section_type":"community"}],"tos_url":"/tos","privacy_policy_url":"https://www.discourse.org/privacy","activity_pub_enabled":true,"activity_pub_publishing_enabled":true,"activity_pub_host":"meta.discourse.org","docs_path":"docs","default_gamification_leaderboard_id":1,"hosting_tier":"enterprise","archetypes":[{"id":"regular","name":"Regular Topic","options":[]},{"id":"banner","name":"Banner Topic","options":[]}],"user_fields":[{"id":2,"name":"Pronouns","description":"Gender Pronouns (he/him, she/her, they/them, etc.)","field_type":"text","editable":true,"required":false,"show_on_profile":true,"show_on_user_card":true,"searchable":false,"position":1}],"auth_providers":[{"name":"facebook","custom_url":null,"pretty_name_override":null,"title_override":null,"frame_width":580,"frame_height":400,"can_connect":true,"can_revoke":true,"icon":"fab-facebook"},{"name":"google_oauth2","custom_url":null,"pretty_name_override":null,"title_override":null,"frame_width":850,"frame_height":500,"can_connect":true,"can_revoke":true,"icon":null},{"name":"github","custom_url":null,"pretty_name_override":null,"title_override":null,"frame_width":null,"frame_height":null,"can_connect":true,"can_revoke":true,"icon":"fab-github"},{"name":"twitter","custom_url":null,"pretty_name_override":null,"title_override":null,"frame_width":null,"frame_height":null,"can_connect":true,"can_revoke":true,"icon":"fab-twitter"},{"name":"discord","custom_url":null,"pretty_name_override":null,"title_override":null,"frame_width":null,"frame_height":null,"can_connect":true,"can_revoke":true,"icon":"fab-discord"},{"name":"apple","custom_url":null,"pretty_name_override":null,"title_override":null,"frame_width":null,"frame_height":null,"can_connect":true,"can_revoke":true,"icon":"fab-apple"}]} \ No newline at end of file diff --git a/spec/jobs/regular/digest_rag_upload_spec.rb b/spec/jobs/regular/digest_rag_upload_spec.rb index d3b1ed584..26a2beb72 100644 --- a/spec/jobs/regular/digest_rag_upload_spec.rb +++ b/spec/jobs/regular/digest_rag_upload_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.describe Jobs::DigestRagUpload do - fab!(:persona) { Fabricate(:ai_persona) } + fab!(:agent) { Fabricate(:ai_agent) } fab!(:upload) { Fabricate(:upload, extension: "txt") } fab!(:image_upload) { Fabricate(:upload, extension: "png") } let(:document_file) { StringIO.new("some text" * 200) } @@ -38,8 +38,8 @@ expect { described_class.new.execute( upload_id: image_upload.id, - target_id: persona.id, - target_type: persona.class.to_s, + target_id: agent.id, + target_type: agent.class.to_s, ) }.to raise_error(Discourse::InvalidAccess) end @@ -47,12 +47,12 @@ context "when processing an upload containing metadata" do it "correctly splits on metadata boundary" do # be explicit here about chunking strategy - persona.update!(rag_chunk_tokens: 100, rag_chunk_overlap_tokens: 10) + agent.update!(rag_chunk_tokens: 100, rag_chunk_overlap_tokens: 10) described_class.new.execute( upload_id: upload_with_metadata.id, - target_id: persona.id, - target_type: persona.class.to_s, + target_id: agent.id, + target_type: agent.class.to_s, ) parsed = +"" @@ -80,8 +80,8 @@ it "splits an upload into chunks" do subject.execute( upload_id: upload.id, - target_id: persona.id, - target_type: persona.class.to_s, + target_id: agent.id, + target_type: agent.class.to_s, ) created_fragment = RagDocumentFragment.last @@ -95,20 +95,20 @@ expect { subject.execute( upload_id: upload.id, - target_id: persona.id, - target_type: persona.class.to_s, + target_id: agent.id, + target_type: agent.class.to_s, ) }.to change(Jobs::GenerateRagEmbeddings.jobs, :size).by(1) end end it "doesn't generate new fragments if we already processed the upload" do - Fabricate(:rag_document_fragment, upload: upload, target: persona) + Fabricate(:rag_document_fragment, upload: upload, target: agent) - previous_count = RagDocumentFragment.where(upload: upload, target: persona).count + previous_count = RagDocumentFragment.where(upload: upload, target: agent).count - subject.execute(upload_id: upload.id, target_id: persona.id, target_type: persona.class.to_s) - updated_count = RagDocumentFragment.where(upload: upload, target: persona).count + subject.execute(upload_id: upload.id, target_id: agent.id, target_type: agent.class.to_s) + updated_count = RagDocumentFragment.where(upload: upload, target: agent).count expect(updated_count).to eq(previous_count) end diff --git a/spec/jobs/regular/generate_rag_embeddings_spec.rb b/spec/jobs/regular/generate_rag_embeddings_spec.rb index 105587459..9058ecc0c 100644 --- a/spec/jobs/regular/generate_rag_embeddings_spec.rb +++ b/spec/jobs/regular/generate_rag_embeddings_spec.rb @@ -6,10 +6,10 @@ let(:expected_embedding) { [0.0038493] * vector_def.dimensions } - fab!(:ai_persona) + fab!(:ai_agent) - let(:rag_document_fragment_1) { Fabricate(:rag_document_fragment, target: ai_persona) } - let(:rag_document_fragment_2) { Fabricate(:rag_document_fragment, target: ai_persona) } + let(:rag_document_fragment_1) { Fabricate(:rag_document_fragment, target: ai_agent) } + let(:rag_document_fragment_2) { Fabricate(:rag_document_fragment, target: ai_agent) } before do SiteSetting.ai_embeddings_selected_model = vector_def.id diff --git a/spec/jobs/regular/stream_discord_reply_spec.rb b/spec/jobs/regular/stream_discord_reply_spec.rb index 1543bb3f7..3f5a78698 100644 --- a/spec/jobs/regular/stream_discord_reply_spec.rb +++ b/spec/jobs/regular/stream_discord_reply_spec.rb @@ -14,22 +14,22 @@ end fab!(:llm_model) - fab!(:persona) { Fabricate(:ai_persona, default_llm_id: llm_model.id) } + fab!(:agent) { Fabricate(:ai_agent, default_llm_id: llm_model.id) } before do SiteSetting.ai_discord_search_enabled = true - SiteSetting.ai_discord_search_mode = "persona" - SiteSetting.ai_discord_search_persona = persona.id + SiteSetting.ai_discord_search_mode = "agent" + SiteSetting.ai_discord_search_agent = agent.id end - it "calls PersonaReplier when search mode is persona" do - expect_any_instance_of(DiscourseAi::Discord::Bot::PersonaReplier).to receive( + it "calls AgentReplier when search mode is agent" do + expect_any_instance_of(DiscourseAi::Discord::Bot::AgentReplier).to receive( :handle_interaction!, ) described_class.new.execute(interaction: interaction) end - it "calls Search when search mode is not persona" do + it "calls Search when search mode is not agent" do SiteSetting.ai_discord_search_mode = "search" expect_any_instance_of(DiscourseAi::Discord::Bot::Search).to receive(:handle_interaction!) described_class.new.execute(interaction: interaction) diff --git a/spec/jobs/regular/stream_discover_reply_spec.rb b/spec/jobs/regular/stream_discover_reply_spec.rb index 4736f03ed..2d18c8d9a 100644 --- a/spec/jobs/regular/stream_discover_reply_spec.rb +++ b/spec/jobs/regular/stream_discover_reply_spec.rb @@ -7,12 +7,12 @@ fab!(:user) fab!(:llm_model) fab!(:group) - fab!(:ai_persona) do - Fabricate(:ai_persona, allowed_group_ids: [group.id], default_llm_id: llm_model.id) + fab!(:ai_agent) do + Fabricate(:ai_agent, allowed_group_ids: [group.id], default_llm_id: llm_model.id) end before do - SiteSetting.ai_bot_discover_persona = ai_persona.id + SiteSetting.ai_bot_discover_agent = ai_agent.id group.add(user) end diff --git a/spec/lib/personas/artifact_update_strategies/diff_spec.rb b/spec/lib/agents/artifact_update_strategies/diff_spec.rb similarity index 98% rename from spec/lib/personas/artifact_update_strategies/diff_spec.rb rename to spec/lib/agents/artifact_update_strategies/diff_spec.rb index d07813ff0..4403f9162 100644 --- a/spec/lib/personas/artifact_update_strategies/diff_spec.rb +++ b/spec/lib/agents/artifact_update_strategies/diff_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::Personas::ArtifactUpdateStrategies::Diff do +RSpec.describe DiscourseAi::Agents::ArtifactUpdateStrategies::Diff do fab!(:user) fab!(:post) fab!(:artifact) { Fabricate(:ai_artifact) } diff --git a/spec/lib/personas/bot_spec.rb b/spec/lib/agents/bot_spec.rb similarity index 79% rename from spec/lib/personas/bot_spec.rb rename to spec/lib/agents/bot_spec.rb index 5578f8b72..c8d6b142d 100644 --- a/spec/lib/personas/bot_spec.rb +++ b/spec/lib/agents/bot_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::Personas::Bot do - subject(:bot) { described_class.as(bot_user, persona: DiscourseAi::Personas::General.new) } +RSpec.describe DiscourseAi::Agents::Bot do + subject(:bot) { described_class.as(bot_user, agent: DiscourseAi::Agents::General.new) } fab!(:admin) fab!(:gpt_4) { Fabricate(:llm_model, name: "gpt-4") } @@ -39,8 +39,8 @@ Group.refresh_automatic_groups! bot_user = DiscourseAi::AiBot::EntryPoint.find_user_from_model(fake.name) - AiPersona.create!( - name: "TestPersona", + AiAgent.create!( + name: "TestAgent", top_p: 0.5, temperature: 0.4, system_prompt: "test", @@ -48,11 +48,11 @@ allowed_group_ids: [Group::AUTO_GROUPS[:trust_level_0]], ) - personaClass = DiscourseAi::Personas::Persona.find_by(user: admin, name: "TestPersona") + agentClass = DiscourseAi::Agents::Agent.find_by(user: admin, name: "TestAgent") - bot = described_class.as(bot_user, persona: personaClass.new) + bot = described_class.as(bot_user, agent: agentClass.new) bot.reply( - DiscourseAi::Personas::BotContext.new(messages: [{ type: :user, content: "test" }]), + DiscourseAi::Agents::BotContext.new(messages: [{ type: :user, content: "test" }]), ) do |_partial, _cancel, _placeholder| # we just need the block so bot has something to call with results end @@ -64,7 +64,7 @@ context "when using function chaining" do it "yields a loading placeholder while proceeds to invoke the command" do - tool = DiscourseAi::Personas::Tools::ListCategories.new({}, bot_user: nil, llm: nil) + tool = DiscourseAi::Agents::Tools::ListCategories.new({}, bot_user: nil, llm: nil) partial_placeholder = +(<<~HTML) #{tool.summary}
@@ -75,7 +75,7 @@ HTML context = - DiscourseAi::Personas::BotContext.new( + DiscourseAi::Agents::BotContext.new( messages: [{ type: :user, content: "Does my site has tags?" }], ) diff --git a/spec/lib/personas/persona_spec.rb b/spec/lib/agents/persona_spec.rb similarity index 63% rename from spec/lib/personas/persona_spec.rb rename to spec/lib/agents/persona_spec.rb index d3e905680..090bc66b3 100644 --- a/spec/lib/personas/persona_spec.rb +++ b/spec/lib/agents/persona_spec.rb @@ -1,11 +1,11 @@ #frozen_string_literal: true -class TestPersona < DiscourseAi::Personas::Persona +class TestAgent < DiscourseAi::Agents::Agent def tools [ - DiscourseAi::Personas::Tools::ListTags, - DiscourseAi::Personas::Tools::Search, - DiscourseAi::Personas::Tools::Image, + DiscourseAi::Agents::Tools::ListTags, + DiscourseAi::Agents::Tools::Search, + DiscourseAi::Agents::Tools::Image, ] end @@ -21,9 +21,9 @@ def system_prompt end end -RSpec.describe DiscourseAi::Personas::Persona do - let :persona do - TestPersona.new +RSpec.describe DiscourseAi::Agents::Agent do + let :agent do + TestAgent.new end let :topic_with_users do @@ -34,13 +34,13 @@ def system_prompt after do # we are rolling back transactions so we can create poison cache - AiPersona.persona_cache.flush! + AiAgent.agent_cache.flush! end let(:resource_url) { "https://path-to-resource" } let(:context) do - DiscourseAi::Personas::BotContext.new( + DiscourseAi::Agents::BotContext.new( site_url: Discourse.base_url, site_title: "test site title", site_description: "test site description", @@ -57,7 +57,7 @@ def system_prompt it "renders the system prompt" do freeze_time - rendered = persona.craft_prompt(context) + rendered = agent.craft_prompt(context) system_message = rendered.messages.first[:content] expect(system_message).to include(Discourse.base_url) @@ -90,7 +90,7 @@ def system_prompt ) tool_instance = - DiscourseAi::Personas::Artist.new.find_tool(tool_call, bot_user: nil, llm: nil, context: nil) + DiscourseAi::Agents::Artist.new.find_tool(tool_call, bot_user: nil, llm: nil, context: nil) expect(tool_instance.parameters[:prompts]).to eq(["cat oil painting", "big car"]) expect(tool_instance.parameters[:aspect_ratio]).to eq("16:9") @@ -109,7 +109,7 @@ def system_prompt ) tool_instance = - DiscourseAi::Personas::General.new.find_tool(tool_call, bot_user: nil, llm: nil, context: nil) + DiscourseAi::Agents::General.new.find_tool(tool_call, bot_user: nil, llm: nil, context: nil) expect(tool_instance.parameters.key?(:status)).to eq(false) @@ -125,7 +125,7 @@ def system_prompt ) tool_instance = - DiscourseAi::Personas::General.new.find_tool(tool_call, bot_user: nil, llm: nil, context: nil) + DiscourseAi::Agents::General.new.find_tool(tool_call, bot_user: nil, llm: nil, context: nil) expect(tool_instance.parameters[:status]).to eq("open") end @@ -143,7 +143,7 @@ def system_prompt ) search = - DiscourseAi::Personas::General.new.find_tool(tool_call, bot_user: nil, llm: nil, context: nil) + DiscourseAi::Agents::General.new.find_tool(tool_call, bot_user: nil, llm: nil, context: nil) expect(search.parameters[:max_posts]).to eq(3) expect(search.parameters[:search_query]).to eq("hello world") @@ -163,17 +163,17 @@ def system_prompt ) tool_instance = - DiscourseAi::Personas::DallE3.new.find_tool(tool_call, bot_user: nil, llm: nil, context: nil) + DiscourseAi::Agents::DallE3.new.find_tool(tool_call, bot_user: nil, llm: nil, context: nil) expect(tool_instance.parameters[:prompts]).to eq(["cat oil painting", "big car"]) end - describe "custom personas" do - it "is able to find custom personas" do + describe "custom agents" do + it "is able to find custom agents" do Group.refresh_automatic_groups! - # define an ai persona everyone can see - persona = - AiPersona.create!( + # define an ai agent everyone can see + agent = + AiAgent.create!( name: "zzzpun_bot", description: "you write puns", system_prompt: "you are pun bot", @@ -181,35 +181,35 @@ def system_prompt allowed_group_ids: [Group::AUTO_GROUPS[:trust_level_0]], ) - custom_persona = DiscourseAi::Personas::Persona.all(user: user).last - expect(custom_persona.name).to eq("zzzpun_bot") - expect(custom_persona.description).to eq("you write puns") + custom_agent = DiscourseAi::Agents::Agent.all(user: user).last + expect(custom_agent.name).to eq("zzzpun_bot") + expect(custom_agent.description).to eq("you write puns") - instance = custom_persona.new - expect(instance.tools).to eq([DiscourseAi::Personas::Tools::Image]) + instance = custom_agent.new + expect(instance.tools).to eq([DiscourseAi::Agents::Tools::Image]) expect(instance.craft_prompt(context).messages.first[:content]).to eq("you are pun bot") # should update - persona.update!(name: "zzzpun_bot2") - custom_persona = DiscourseAi::Personas::Persona.all(user: user).last - expect(custom_persona.name).to eq("zzzpun_bot2") + agent.update!(name: "zzzpun_bot2") + custom_agent = DiscourseAi::Agents::Agent.all(user: user).last + expect(custom_agent.name).to eq("zzzpun_bot2") # can be disabled - persona.update!(enabled: false) - last_persona = DiscourseAi::Personas::Persona.all(user: user).last - expect(last_persona.name).not_to eq("zzzpun_bot2") + agent.update!(enabled: false) + last_agent = DiscourseAi::Agents::Agent.all(user: user).last + expect(last_agent.name).not_to eq("zzzpun_bot2") - persona.update!(enabled: true) + agent.update!(enabled: true) # no groups have access - persona.update!(allowed_group_ids: []) + agent.update!(allowed_group_ids: []) - last_persona = DiscourseAi::Personas::Persona.all(user: user).last - expect(last_persona.name).not_to eq("zzzpun_bot2") + last_agent = DiscourseAi::Agents::Agent.all(user: user).last + expect(last_agent.name).not_to eq("zzzpun_bot2") end end - describe "available personas" do - it "includes all personas by default" do + describe "available agents" do + it "includes all agents by default" do Group.refresh_automatic_groups! # must be enabled to see it @@ -218,54 +218,54 @@ def system_prompt SiteSetting.ai_google_custom_search_cx = "abc123" # should be ordered by priority and then alpha - expect(DiscourseAi::Personas::Persona.all(user: user).map(&:superclass)).to contain_exactly( - DiscourseAi::Personas::General, - DiscourseAi::Personas::Artist, - DiscourseAi::Personas::Creative, - DiscourseAi::Personas::DiscourseHelper, - DiscourseAi::Personas::GithubHelper, - DiscourseAi::Personas::Researcher, - DiscourseAi::Personas::SettingsExplorer, - DiscourseAi::Personas::SqlHelper, + expect(DiscourseAi::Agents::Agent.all(user: user).map(&:superclass)).to contain_exactly( + DiscourseAi::Agents::General, + DiscourseAi::Agents::Artist, + DiscourseAi::Agents::Creative, + DiscourseAi::Agents::DiscourseHelper, + DiscourseAi::Agents::GithubHelper, + DiscourseAi::Agents::Researcher, + DiscourseAi::Agents::SettingsExplorer, + DiscourseAi::Agents::SqlHelper, ) # it should allow staff access to WebArtifactCreator - expect(DiscourseAi::Personas::Persona.all(user: admin).map(&:superclass)).to contain_exactly( - DiscourseAi::Personas::General, - DiscourseAi::Personas::Artist, - DiscourseAi::Personas::Creative, - DiscourseAi::Personas::DiscourseHelper, - DiscourseAi::Personas::GithubHelper, - DiscourseAi::Personas::Researcher, - DiscourseAi::Personas::SettingsExplorer, - DiscourseAi::Personas::SqlHelper, - DiscourseAi::Personas::WebArtifactCreator, + expect(DiscourseAi::Agents::Agent.all(user: admin).map(&:superclass)).to contain_exactly( + DiscourseAi::Agents::General, + DiscourseAi::Agents::Artist, + DiscourseAi::Agents::Creative, + DiscourseAi::Agents::DiscourseHelper, + DiscourseAi::Agents::GithubHelper, + DiscourseAi::Agents::Researcher, + DiscourseAi::Agents::SettingsExplorer, + DiscourseAi::Agents::SqlHelper, + DiscourseAi::Agents::WebArtifactCreator, ) - # omits personas if key is missing + # omits agents if key is missing SiteSetting.ai_stability_api_key = "" SiteSetting.ai_google_custom_search_api_key = "" SiteSetting.ai_artifact_security = "disabled" - expect(DiscourseAi::Personas::Persona.all(user: admin).map(&:superclass)).to contain_exactly( - DiscourseAi::Personas::General, - DiscourseAi::Personas::SqlHelper, - DiscourseAi::Personas::SettingsExplorer, - DiscourseAi::Personas::Creative, - DiscourseAi::Personas::DiscourseHelper, - DiscourseAi::Personas::GithubHelper, + expect(DiscourseAi::Agents::Agent.all(user: admin).map(&:superclass)).to contain_exactly( + DiscourseAi::Agents::General, + DiscourseAi::Agents::SqlHelper, + DiscourseAi::Agents::SettingsExplorer, + DiscourseAi::Agents::Creative, + DiscourseAi::Agents::DiscourseHelper, + DiscourseAi::Agents::GithubHelper, ) - AiPersona.find( - DiscourseAi::Personas::Persona.system_personas[DiscourseAi::Personas::General], + AiAgent.find( + DiscourseAi::Agents::Agent.system_agents[DiscourseAi::Agents::General], ).update!(enabled: false) - expect(DiscourseAi::Personas::Persona.all(user: user).map(&:superclass)).to contain_exactly( - DiscourseAi::Personas::SqlHelper, - DiscourseAi::Personas::SettingsExplorer, - DiscourseAi::Personas::Creative, - DiscourseAi::Personas::DiscourseHelper, - DiscourseAi::Personas::GithubHelper, + expect(DiscourseAi::Agents::Agent.all(user: user).map(&:superclass)).to contain_exactly( + DiscourseAi::Agents::SqlHelper, + DiscourseAi::Agents::SettingsExplorer, + DiscourseAi::Agents::Creative, + DiscourseAi::Agents::DiscourseHelper, + DiscourseAi::Agents::GithubHelper, ) end end @@ -279,19 +279,19 @@ def system_prompt SiteSetting.ai_embeddings_enabled = true end - let(:ai_persona) { DiscourseAi::Personas::Persona.all(user: user).first.new } + let(:ai_agent) { DiscourseAi::Agents::Agent.all(user: user).first.new } let(:with_cc) do context.messages = [{ content: "Tell me the time", type: :user }] context end - context "when a persona has no uploads" do + context "when a agent has no uploads" do it "doesn't include RAG guidance" do guidance_fragment = "The following texts will give you additional guidance to elaborate a response." - expect(ai_persona.craft_prompt(with_cc).messages.first[:content]).not_to include( + expect(ai_agent.craft_prompt(with_cc).messages.first[:content]).not_to include( guidance_fragment, ) end @@ -306,19 +306,19 @@ def system_prompt context_embedding = vector_def.dimensions.times.map { rand(-1.0...1.0) } EmbeddingsGenerationStubs.hugging_face_service(consolidated_question, context_embedding) - custom_ai_persona = + custom_ai_agent = Fabricate( - :ai_persona, + :ai_agent, name: "custom", rag_conversation_chunks: 3, allowed_group_ids: [Group::AUTO_GROUPS[:trust_level_0]], question_consolidator_llm_id: llm_model.id, ) - UploadReference.ensure_exist!(target: custom_ai_persona, upload_ids: [upload.id]) + UploadReference.ensure_exist!(target: custom_ai_agent, upload_ids: [upload.id]) - custom_persona = - DiscourseAi::Personas::Persona.find_by(id: custom_ai_persona.id, user: user).new + custom_agent = + DiscourseAi::Agents::Agent.find_by(id: custom_ai_agent.id, user: user).new # this means that we will consolidate context.messages = [ @@ -328,7 +328,7 @@ def system_prompt ] DiscourseAi::Completions::Endpoints::Fake.with_fake_content(consolidated_question) do - custom_persona.craft_prompt(context).messages.first[:content] + custom_agent.craft_prompt(context).messages.first[:content] end message = @@ -341,11 +341,11 @@ def system_prompt end end - context "when a persona has RAG uploads" do + context "when a agent has RAG uploads" do let(:embedding_value) { 0.04381 } let(:prompt_cc_embeddings) { [embedding_value] * vector_def.dimensions } - def stub_fragments(fragment_count, persona: ai_persona) + def stub_fragments(fragment_count, agent: ai_agent) schema = DiscourseAi::Embeddings::Schema.for(RagDocumentFragment) fragment_count.times do |i| @@ -353,8 +353,8 @@ def stub_fragments(fragment_count, persona: ai_persona) Fabricate( :rag_document_fragment, fragment: "fragment-n#{i}", - target_id: persona.id, - target_type: "AiPersona", + target_id: agent.id, + target_type: "AiAgent", upload: upload, ) @@ -366,8 +366,8 @@ def stub_fragments(fragment_count, persona: ai_persona) end before do - stored_ai_persona = AiPersona.find(ai_persona.id) - UploadReference.ensure_exist!(target: stored_ai_persona, upload_ids: [upload.id]) + stored_ai_agent = AiAgent.find(ai_agent.id) + UploadReference.ensure_exist!(target: stored_ai_agent, upload_ids: [upload.id]) EmbeddingsGenerationStubs.hugging_face_service( with_cc.messages.dig(0, :content), @@ -375,26 +375,26 @@ def stub_fragments(fragment_count, persona: ai_persona) ) end - context "when persona allows for less fragments" do + context "when agent allows for less fragments" do it "will only pick 3 fragments" do - custom_ai_persona = + custom_ai_agent = Fabricate( - :ai_persona, + :ai_agent, name: "custom", rag_conversation_chunks: 3, allowed_group_ids: [Group::AUTO_GROUPS[:trust_level_0]], ) - stub_fragments(3, persona: custom_ai_persona) + stub_fragments(3, agent: custom_ai_agent) - UploadReference.ensure_exist!(target: custom_ai_persona, upload_ids: [upload.id]) + UploadReference.ensure_exist!(target: custom_ai_agent, upload_ids: [upload.id]) - custom_persona = - DiscourseAi::Personas::Persona.find_by(id: custom_ai_persona.id, user: user).new + custom_agent = + DiscourseAi::Agents::Agent.find_by(id: custom_ai_agent.id, user: user).new - expect(custom_persona.class.rag_conversation_chunks).to eq(3) + expect(custom_agent.class.rag_conversation_chunks).to eq(3) - crafted_system_prompt = custom_persona.craft_prompt(with_cc).messages.first[:content] + crafted_system_prompt = custom_agent.craft_prompt(with_cc).messages.first[:content] expect(crafted_system_prompt).to include("fragment-n0") expect(crafted_system_prompt).to include("fragment-n1") @@ -420,7 +420,7 @@ def stub_fragments(fragment_count, persona: ai_persona) body: JSON.dump(expected_reranked), ) - crafted_system_prompt = ai_persona.craft_prompt(with_cc).messages.first[:content] + crafted_system_prompt = ai_agent.craft_prompt(with_cc).messages.first[:content] expect(crafted_system_prompt).to include("fragment-n14") expect(crafted_system_prompt).to include("fragment-n13") @@ -433,7 +433,7 @@ def stub_fragments(fragment_count, persona: ai_persona) before { stub_fragments(10) } it "picks the first 10 candidates from the similarity search" do - crafted_system_prompt = ai_persona.craft_prompt(with_cc).messages.first[:content] + crafted_system_prompt = ai_agent.craft_prompt(with_cc).messages.first[:content] expect(crafted_system_prompt).to include("fragment-n0") expect(crafted_system_prompt).to include("fragment-n1") @@ -443,20 +443,20 @@ def stub_fragments(fragment_count, persona: ai_persona) end end - context "when the persona has examples" do - fab!(:examples_persona) do + context "when the agent has examples" do + fab!(:examples_agent) do Fabricate( - :ai_persona, + :ai_agent, examples: [["User message", "assistant response"]], allowed_group_ids: [Group::AUTO_GROUPS[:trust_level_0]], ) end it "includes them before the context messages" do - custom_persona = - DiscourseAi::Personas::Persona.find_by(id: examples_persona.id, user: user).new + custom_agent = + DiscourseAi::Agents::Agent.find_by(id: examples_agent.id, user: user).new - post_system_prompt_msgs = custom_persona.craft_prompt(with_cc).messages.last(3) + post_system_prompt_msgs = custom_agent.craft_prompt(with_cc).messages.last(3) expect(post_system_prompt_msgs).to contain_exactly( { content: "User message", type: :user }, diff --git a/spec/lib/personas/question_consolidator_spec.rb b/spec/lib/agents/question_consolidator_spec.rb similarity index 94% rename from spec/lib/personas/question_consolidator_spec.rb rename to spec/lib/agents/question_consolidator_spec.rb index 7fe543993..08af4a03b 100644 --- a/spec/lib/personas/question_consolidator_spec.rb +++ b/spec/lib/agents/question_consolidator_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::Personas::QuestionConsolidator do +RSpec.describe DiscourseAi::Agents::QuestionConsolidator do let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{Fabricate(:fake_model).id}") } let(:fake_endpoint) { DiscourseAi::Completions::Endpoints::Fake } diff --git a/spec/lib/personas/researcher_spec.rb b/spec/lib/agents/researcher_spec.rb similarity index 51% rename from spec/lib/personas/researcher_spec.rb rename to spec/lib/agents/researcher_spec.rb index d216d6c0c..56450d9ee 100644 --- a/spec/lib/personas/researcher_spec.rb +++ b/spec/lib/agents/researcher_spec.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::Personas::Researcher do +RSpec.describe DiscourseAi::Agents::Researcher do let :researcher do subject end it "renders schema" do expect(researcher.tools).to eq( - [DiscourseAi::Personas::Tools::Google, DiscourseAi::Personas::Tools::WebBrowser], + [DiscourseAi::Agents::Tools::Google, DiscourseAi::Agents::Tools::WebBrowser], ) end end diff --git a/spec/lib/personas/settings_explorer_spec.rb b/spec/lib/agents/settings_explorer_spec.rb similarity index 58% rename from spec/lib/personas/settings_explorer_spec.rb rename to spec/lib/agents/settings_explorer_spec.rb index 24f152243..6204189eb 100644 --- a/spec/lib/personas/settings_explorer_spec.rb +++ b/spec/lib/agents/settings_explorer_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::Personas::SettingsExplorer do +RSpec.describe DiscourseAi::Agents::SettingsExplorer do let :settings_explorer do subject end @@ -9,12 +9,12 @@ prompt = settings_explorer.system_prompt # check we do not render plugin settings - expect(prompt).not_to include("ai_bot_enabled_personas") + expect(prompt).not_to include("ai_bot_enabled_agents") expect(prompt).to include("site_description") expect(settings_explorer.tools).to eq( - [DiscourseAi::Personas::Tools::SettingContext, DiscourseAi::Personas::Tools::SearchSettings], + [DiscourseAi::Agents::Tools::SettingContext, DiscourseAi::Agents::Tools::SearchSettings], ) end end diff --git a/spec/lib/personas/sql_helper_spec.rb b/spec/lib/agents/sql_helper_spec.rb similarity index 74% rename from spec/lib/personas/sql_helper_spec.rb rename to spec/lib/agents/sql_helper_spec.rb index d2f1614e1..c1a0385f4 100644 --- a/spec/lib/personas/sql_helper_spec.rb +++ b/spec/lib/agents/sql_helper_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::Personas::SqlHelper do +RSpec.describe DiscourseAi::Agents::SqlHelper do let :sql_helper do subject end @@ -12,6 +12,6 @@ expect(prompt).not_to include("translation_key") # not a priority table expect(prompt).to include("user_api_keys") # not a priority table - expect(sql_helper.tools).to eq([DiscourseAi::Personas::Tools::DbSchema]) + expect(sql_helper.tools).to eq([DiscourseAi::Agents::Tools::DbSchema]) end end diff --git a/spec/lib/personas/tools/create_artifact_spec.rb b/spec/lib/agents/tools/create_artifact_spec.rb similarity index 92% rename from spec/lib/personas/tools/create_artifact_spec.rb rename to spec/lib/agents/tools/create_artifact_spec.rb index 929e54ef5..d5224d984 100644 --- a/spec/lib/personas/tools/create_artifact_spec.rb +++ b/spec/lib/agents/tools/create_artifact_spec.rb @@ -1,6 +1,6 @@ #frozen_string_literal: true -RSpec.describe DiscourseAi::Personas::Tools::CreateArtifact do +RSpec.describe DiscourseAi::Agents::Tools::CreateArtifact do fab!(:llm_model) let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } fab!(:post) @@ -34,7 +34,7 @@ { html_body: "hello" }, bot_user: Fabricate(:user), llm: llm, - context: DiscourseAi::Personas::BotContext.new(post: post), + context: DiscourseAi::Agents::BotContext.new(post: post), ) tool.parameters = { name: "hello", specification: "hello spec" } diff --git a/spec/lib/personas/tools/create_image_spec.rb b/spec/lib/agents/tools/create_image_spec.rb similarity index 98% rename from spec/lib/personas/tools/create_image_spec.rb rename to spec/lib/agents/tools/create_image_spec.rb index 0aa18fea0..6fda43e5a 100644 --- a/spec/lib/personas/tools/create_image_spec.rb +++ b/spec/lib/agents/tools/create_image_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::Personas::Tools::CreateImage do +RSpec.describe DiscourseAi::Agents::Tools::CreateImage do let(:prompts) { ["a watercolor painting", "an abstract design"] } fab!(:gpt_35_turbo) { Fabricate(:llm_model, name: "gpt-3.5-turbo") } diff --git a/spec/lib/personas/tools/dall_e_spec.rb b/spec/lib/agents/tools/dall_e_spec.rb similarity index 98% rename from spec/lib/personas/tools/dall_e_spec.rb rename to spec/lib/agents/tools/dall_e_spec.rb index 50d4ab72d..52628755e 100644 --- a/spec/lib/personas/tools/dall_e_spec.rb +++ b/spec/lib/agents/tools/dall_e_spec.rb @@ -1,6 +1,6 @@ #frozen_string_literal: true -RSpec.describe DiscourseAi::Personas::Tools::DallE do +RSpec.describe DiscourseAi::Agents::Tools::DallE do let(:prompts) { ["a pink cow", "a red cow"] } fab!(:gpt_35_turbo) { Fabricate(:llm_model, name: "gpt-3.5-turbo") } diff --git a/spec/lib/personas/tools/db_schema_spec.rb b/spec/lib/agents/tools/db_schema_spec.rb similarity index 92% rename from spec/lib/personas/tools/db_schema_spec.rb rename to spec/lib/agents/tools/db_schema_spec.rb index 643e3fe7e..7e6bb1315 100644 --- a/spec/lib/personas/tools/db_schema_spec.rb +++ b/spec/lib/agents/tools/db_schema_spec.rb @@ -1,6 +1,6 @@ #frozen_string_literal: true -RSpec.describe DiscourseAi::Personas::Tools::DbSchema do +RSpec.describe DiscourseAi::Agents::Tools::DbSchema do fab!(:llm_model) let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) } let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } diff --git a/spec/lib/personas/tools/discourse_meta_search_spec.rb b/spec/lib/agents/tools/discourse_meta_search_spec.rb similarity index 97% rename from spec/lib/personas/tools/discourse_meta_search_spec.rb rename to spec/lib/agents/tools/discourse_meta_search_spec.rb index 1ccc4d4db..be66646a2 100644 --- a/spec/lib/personas/tools/discourse_meta_search_spec.rb +++ b/spec/lib/agents/tools/discourse_meta_search_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::Personas::Tools::DiscourseMetaSearch do +RSpec.describe DiscourseAi::Agents::Tools::DiscourseMetaSearch do before { SiteSetting.ai_bot_enabled = true } fab!(:llm_model) { Fabricate(:llm_model, max_prompt_tokens: 8192) } diff --git a/spec/lib/personas/tools/edit_image_spec.rb b/spec/lib/agents/tools/edit_image_spec.rb similarity index 98% rename from spec/lib/personas/tools/edit_image_spec.rb rename to spec/lib/agents/tools/edit_image_spec.rb index 4242aec48..a18aedcb6 100644 --- a/spec/lib/personas/tools/edit_image_spec.rb +++ b/spec/lib/agents/tools/edit_image_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::Personas::Tools::EditImage do +RSpec.describe DiscourseAi::Agents::Tools::EditImage do fab!(:gpt_35_turbo) { Fabricate(:llm_model, name: "gpt-3.5-turbo") } before do diff --git a/spec/lib/personas/tools/github_file_content_spec.rb b/spec/lib/agents/tools/github_file_content_spec.rb similarity index 97% rename from spec/lib/personas/tools/github_file_content_spec.rb rename to spec/lib/agents/tools/github_file_content_spec.rb index 4186dd01e..f06934557 100644 --- a/spec/lib/personas/tools/github_file_content_spec.rb +++ b/spec/lib/agents/tools/github_file_content_spec.rb @@ -2,7 +2,7 @@ require "rails_helper" -RSpec.describe DiscourseAi::Personas::Tools::GithubFileContent do +RSpec.describe DiscourseAi::Agents::Tools::GithubFileContent do fab!(:llm_model) let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } diff --git a/spec/lib/personas/tools/github_pull_request_diff_spec.rb b/spec/lib/agents/tools/github_pull_request_diff_spec.rb similarity index 98% rename from spec/lib/personas/tools/github_pull_request_diff_spec.rb rename to spec/lib/agents/tools/github_pull_request_diff_spec.rb index e8b3d2266..cf6826ce2 100644 --- a/spec/lib/personas/tools/github_pull_request_diff_spec.rb +++ b/spec/lib/agents/tools/github_pull_request_diff_spec.rb @@ -2,7 +2,7 @@ require "rails_helper" -RSpec.describe DiscourseAi::Personas::Tools::GithubPullRequestDiff do +RSpec.describe DiscourseAi::Agents::Tools::GithubPullRequestDiff do let(:bot_user) { Fabricate(:user) } fab!(:llm_model) let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } diff --git a/spec/lib/personas/tools/github_search_code_spec.rb b/spec/lib/agents/tools/github_search_code_spec.rb similarity index 97% rename from spec/lib/personas/tools/github_search_code_spec.rb rename to spec/lib/agents/tools/github_search_code_spec.rb index b8fbca274..14b401a22 100644 --- a/spec/lib/personas/tools/github_search_code_spec.rb +++ b/spec/lib/agents/tools/github_search_code_spec.rb @@ -2,7 +2,7 @@ require "rails_helper" -RSpec.describe DiscourseAi::Personas::Tools::GithubSearchCode do +RSpec.describe DiscourseAi::Agents::Tools::GithubSearchCode do let(:bot_user) { Fabricate(:user) } fab!(:llm_model) let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } diff --git a/spec/lib/personas/tools/github_search_files_spec.rb b/spec/lib/agents/tools/github_search_files_spec.rb similarity index 97% rename from spec/lib/personas/tools/github_search_files_spec.rb rename to spec/lib/agents/tools/github_search_files_spec.rb index cc6926fd6..06def4ba1 100644 --- a/spec/lib/personas/tools/github_search_files_spec.rb +++ b/spec/lib/agents/tools/github_search_files_spec.rb @@ -2,7 +2,7 @@ require "rails_helper" -RSpec.describe DiscourseAi::Personas::Tools::GithubSearchFiles do +RSpec.describe DiscourseAi::Agents::Tools::GithubSearchFiles do fab!(:llm_model) let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } diff --git a/spec/lib/personas/tools/google_spec.rb b/spec/lib/agents/tools/google_spec.rb similarity index 97% rename from spec/lib/personas/tools/google_spec.rb rename to spec/lib/agents/tools/google_spec.rb index 5062cea97..bf87bb781 100644 --- a/spec/lib/personas/tools/google_spec.rb +++ b/spec/lib/agents/tools/google_spec.rb @@ -1,6 +1,6 @@ #frozen_string_literal: true -RSpec.describe DiscourseAi::Personas::Tools::Google do +RSpec.describe DiscourseAi::Agents::Tools::Google do fab!(:llm_model) let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) } let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } @@ -36,7 +36,7 @@ { query: "some search term" }, bot_user: bot_user, llm: llm, - persona_options: { + agent_options: { "base_query" => base_query, }, ) diff --git a/spec/lib/personas/tools/image_spec.rb b/spec/lib/agents/tools/image_spec.rb similarity index 94% rename from spec/lib/personas/tools/image_spec.rb rename to spec/lib/agents/tools/image_spec.rb index 342c9f676..278c4c333 100644 --- a/spec/lib/personas/tools/image_spec.rb +++ b/spec/lib/agents/tools/image_spec.rb @@ -1,6 +1,6 @@ #frozen_string_literal: true -RSpec.describe DiscourseAi::Personas::Tools::Image do +RSpec.describe DiscourseAi::Agents::Tools::Image do let(:progress_blk) { Proc.new {} } let(:prompts) { ["a pink cow", "a red cow"] } @@ -9,7 +9,7 @@ { prompts: prompts, seeds: [99, 32] }, bot_user: bot_user, llm: llm, - context: DiscourseAi::Personas::BotContext.new, + context: DiscourseAi::Agents::BotContext.new, ) end diff --git a/spec/lib/personas/tools/javascript_evaluator_spec.rb b/spec/lib/agents/tools/javascript_evaluator_spec.rb similarity index 97% rename from spec/lib/personas/tools/javascript_evaluator_spec.rb rename to spec/lib/agents/tools/javascript_evaluator_spec.rb index cae05ee9c..350b601cb 100644 --- a/spec/lib/personas/tools/javascript_evaluator_spec.rb +++ b/spec/lib/agents/tools/javascript_evaluator_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::Personas::Tools::JavascriptEvaluator do +RSpec.describe DiscourseAi::Agents::Tools::JavascriptEvaluator do fab!(:llm_model) let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) } let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } diff --git a/spec/lib/personas/tools/list_categories_spec.rb b/spec/lib/agents/tools/list_categories_spec.rb similarity index 90% rename from spec/lib/personas/tools/list_categories_spec.rb rename to spec/lib/agents/tools/list_categories_spec.rb index bcda21233..e96029bb3 100644 --- a/spec/lib/personas/tools/list_categories_spec.rb +++ b/spec/lib/agents/tools/list_categories_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::Personas::Tools::ListCategories do +RSpec.describe DiscourseAi::Agents::Tools::ListCategories do fab!(:llm_model) let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) } let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } diff --git a/spec/lib/personas/tools/list_tags_spec.rb b/spec/lib/agents/tools/list_tags_spec.rb similarity index 92% rename from spec/lib/personas/tools/list_tags_spec.rb rename to spec/lib/agents/tools/list_tags_spec.rb index b8f4ed5cd..b20d3f1e2 100644 --- a/spec/lib/personas/tools/list_tags_spec.rb +++ b/spec/lib/agents/tools/list_tags_spec.rb @@ -1,6 +1,6 @@ #frozen_string_literal: true -RSpec.describe DiscourseAi::Personas::Tools::ListTags do +RSpec.describe DiscourseAi::Agents::Tools::ListTags do fab!(:llm_model) let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) } let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } diff --git a/spec/lib/personas/tools/random_picker_spec.rb b/spec/lib/agents/tools/random_picker_spec.rb similarity index 96% rename from spec/lib/personas/tools/random_picker_spec.rb rename to spec/lib/agents/tools/random_picker_spec.rb index 65f7c7f29..e55fb7afa 100644 --- a/spec/lib/personas/tools/random_picker_spec.rb +++ b/spec/lib/agents/tools/random_picker_spec.rb @@ -2,7 +2,7 @@ require "rails_helper" -RSpec.describe DiscourseAi::Personas::Tools::RandomPicker do +RSpec.describe DiscourseAi::Agents::Tools::RandomPicker do describe "#invoke" do subject { described_class.new({ options: options }, bot_user: nil, llm: nil).invoke } diff --git a/spec/lib/personas/tools/read_artifact_spec.rb b/spec/lib/agents/tools/read_artifact_spec.rb similarity index 90% rename from spec/lib/personas/tools/read_artifact_spec.rb rename to spec/lib/agents/tools/read_artifact_spec.rb index 279dd18e7..26e201fc7 100644 --- a/spec/lib/personas/tools/read_artifact_spec.rb +++ b/spec/lib/agents/tools/read_artifact_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::Personas::Tools::ReadArtifact do +RSpec.describe DiscourseAi::Agents::Tools::ReadArtifact do fab!(:llm_model) let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) } fab!(:post) @@ -25,7 +25,7 @@ { url: "#{Discourse.base_url}/discourse-ai/ai-bot/artifacts/#{artifact.id}" }, bot_user: bot_user, llm: llm_model.to_llm, - context: DiscourseAi::Personas::BotContext.new(post: post), + context: DiscourseAi::Agents::BotContext.new(post: post), ) result = tool.invoke {} @@ -44,7 +44,7 @@ { url: "invalid-url" }, bot_user: bot_user, llm: llm_model.to_llm, - context: DiscourseAi::Personas::BotContext.new(post: post), + context: DiscourseAi::Agents::BotContext.new(post: post), ) result = tool.invoke {} @@ -58,7 +58,7 @@ { url: "#{Discourse.base_url}/discourse-ai/ai-bot/artifacts/99999" }, bot_user: bot_user, llm: llm_model.to_llm, - context: DiscourseAi::Personas::BotContext.new(post: post), + context: DiscourseAi::Agents::BotContext.new(post: post), ) result = tool.invoke {} @@ -91,7 +91,7 @@ { url: "https://example.com" }, bot_user: bot_user, llm: llm_model.to_llm, - context: DiscourseAi::Personas::BotContext.new(post: post), + context: DiscourseAi::Agents::BotContext.new(post: post), ) result = tool.invoke {} @@ -120,7 +120,7 @@ { url: "https://example.com" }, bot_user: bot_user, llm: llm_model.to_llm, - context: DiscourseAi::Personas::BotContext.new(post: post), + context: DiscourseAi::Agents::BotContext.new(post: post), ) result = tool.invoke {} diff --git a/spec/lib/personas/tools/read_spec.rb b/spec/lib/agents/tools/read_spec.rb similarity index 93% rename from spec/lib/personas/tools/read_spec.rb rename to spec/lib/agents/tools/read_spec.rb index 2affc1f4e..4f4986d84 100644 --- a/spec/lib/personas/tools/read_spec.rb +++ b/spec/lib/agents/tools/read_spec.rb @@ -1,6 +1,6 @@ #frozen_string_literal: true -RSpec.describe DiscourseAi::Personas::Tools::Read do +RSpec.describe DiscourseAi::Agents::Tools::Read do fab!(:llm_model) let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) } let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } @@ -53,10 +53,10 @@ { topic_id: topic_with_tags.id, post_numbers: [post1.post_number] }, bot_user: bot_user, llm: llm, - persona_options: { + agent_options: { "read_private" => true, }, - context: DiscourseAi::Personas::BotContext.new(user: admin), + context: DiscourseAi::Agents::BotContext.new(user: admin), ) results = tool.invoke expect(results[:content]).to include("hello there") @@ -66,7 +66,7 @@ { topic_id: topic_with_tags.id, post_numbers: [post1.post_number] }, bot_user: bot_user, llm: llm, - context: DiscourseAi::Personas::BotContext.new(user: admin), + context: DiscourseAi::Agents::BotContext.new(user: admin), ) results = tool.invoke diff --git a/spec/lib/personas/tools/researcher_spec.rb b/spec/lib/agents/tools/researcher_spec.rb similarity index 91% rename from spec/lib/personas/tools/researcher_spec.rb rename to spec/lib/agents/tools/researcher_spec.rb index 23ed98a7d..07b105177 100644 --- a/spec/lib/personas/tools/researcher_spec.rb +++ b/spec/lib/agents/tools/researcher_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::Personas::Tools::Researcher do +RSpec.describe DiscourseAi::Agents::Tools::Researcher do before { SearchIndexer.enable } after { SearchIndexer.disable } @@ -28,7 +28,7 @@ { dry_run: true, filter: "topic:#{topic_with_tags.id}", goals: "analyze topic content" }, bot_user: bot_user, llm: llm, - context: DiscourseAi::Personas::BotContext.new(user: user, post: post), + context: DiscourseAi::Agents::BotContext.new(user: user, post: post), ) results = researcher.invoke(&progress_blk) expect(results[:number_of_posts]).to eq(1) @@ -40,7 +40,7 @@ { filter: "tag:research after:2023", goals: "analyze post patterns", dry_run: true }, bot_user: bot_user, llm: llm, - context: DiscourseAi::Personas::BotContext.new(user: user, post: post), + context: DiscourseAi::Agents::BotContext.new(user: user, post: post), ) results = researcher.invoke(&progress_blk) @@ -66,7 +66,7 @@ researcher = described_class.new( { filter: "category:research-category" }, - persona_options: { + agent_options: { "max_results" => "50", }, bot_user: bot_user, @@ -82,7 +82,7 @@ { filter: "invalidfilter tag:research", goals: "analyze content" }, bot_user: bot_user, llm: llm, - context: DiscourseAi::Personas::BotContext.new(user: user, post: post), + context: DiscourseAi::Agents::BotContext.new(user: user, post: post), ) results = researcher.invoke(&progress_blk) @@ -110,7 +110,7 @@ }, bot_user: bot_user, llm: llm, - context: DiscourseAi::Personas::BotContext.new(user: user, post: post), + context: DiscourseAi::Agents::BotContext.new(user: user, post: post), ) responses = 10.times.map { |i| ["Found: Relevant content #{i + 1}"] } diff --git a/spec/lib/personas/tools/search_settings_spec.rb b/spec/lib/agents/tools/search_settings_spec.rb similarity index 97% rename from spec/lib/personas/tools/search_settings_spec.rb rename to spec/lib/agents/tools/search_settings_spec.rb index f3cd4356e..2c1b0f1ff 100644 --- a/spec/lib/personas/tools/search_settings_spec.rb +++ b/spec/lib/agents/tools/search_settings_spec.rb @@ -1,6 +1,6 @@ #frozen_string_literal: true -RSpec.describe DiscourseAi::Personas::Tools::SearchSettings do +RSpec.describe DiscourseAi::Agents::Tools::SearchSettings do fab!(:llm_model) let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) } let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } diff --git a/spec/lib/personas/tools/search_spec.rb b/spec/lib/agents/tools/search_spec.rb similarity index 96% rename from spec/lib/personas/tools/search_spec.rb rename to spec/lib/agents/tools/search_spec.rb index c28696f9f..9788dc992 100644 --- a/spec/lib/personas/tools/search_spec.rb +++ b/spec/lib/agents/tools/search_spec.rb @@ -1,6 +1,6 @@ #frozen_string_literal: true -RSpec.describe DiscourseAi::Personas::Tools::Search do +RSpec.describe DiscourseAi::Agents::Tools::Search do before { SearchIndexer.enable } after { SearchIndexer.disable } @@ -40,8 +40,8 @@ before { SiteSetting.ai_bot_enabled = true } describe "#invoke" do - it "can retrieve options from persona correctly" do - persona_options = { + it "can retrieve options from agent correctly" do + agent_options = { "base_query" => "#funny", "search_private" => "true", "max_results" => "10", @@ -57,10 +57,10 @@ search = described_class.new( { order: "latest" }, - persona_options: persona_options, + agent_options: agent_options, bot_user: bot_user, llm: llm, - context: DiscourseAi::Personas::BotContext.new(user: user), + context: DiscourseAi::Agents::BotContext.new(user: user), ) expect(search.options[:base_query]).to eq("#funny") diff --git a/spec/lib/personas/tools/setting_context_spec.rb b/spec/lib/agents/tools/setting_context_spec.rb similarity index 95% rename from spec/lib/personas/tools/setting_context_spec.rb rename to spec/lib/agents/tools/setting_context_spec.rb index 20e26b641..4c54c41fc 100644 --- a/spec/lib/personas/tools/setting_context_spec.rb +++ b/spec/lib/agents/tools/setting_context_spec.rb @@ -8,7 +8,7 @@ def has_rg? end end -RSpec.describe DiscourseAi::Personas::Tools::SettingContext, if: has_rg? do +RSpec.describe DiscourseAi::Agents::Tools::SettingContext, if: has_rg? do fab!(:llm_model) let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) } diff --git a/spec/lib/personas/tools/summarize_spec.rb b/spec/lib/agents/tools/summarize_spec.rb similarity index 96% rename from spec/lib/personas/tools/summarize_spec.rb rename to spec/lib/agents/tools/summarize_spec.rb index 2bda3cd37..4287ceb8a 100644 --- a/spec/lib/personas/tools/summarize_spec.rb +++ b/spec/lib/agents/tools/summarize_spec.rb @@ -1,6 +1,6 @@ #frozen_string_literal: true -RSpec.describe DiscourseAi::Personas::Tools::Summarize do +RSpec.describe DiscourseAi::Agents::Tools::Summarize do fab!(:llm_model) let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) } let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } diff --git a/spec/lib/personas/tools/time_spec.rb b/spec/lib/agents/tools/time_spec.rb similarity index 92% rename from spec/lib/personas/tools/time_spec.rb rename to spec/lib/agents/tools/time_spec.rb index e92a32ad5..b16ea5939 100644 --- a/spec/lib/personas/tools/time_spec.rb +++ b/spec/lib/agents/tools/time_spec.rb @@ -1,6 +1,6 @@ #frozen_string_literal: true -RSpec.describe DiscourseAi::Personas::Tools::Time do +RSpec.describe DiscourseAi::Agents::Tools::Time do fab!(:llm_model) let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) } let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } diff --git a/spec/lib/personas/tools/tool_spec.rb b/spec/lib/agents/tools/tool_spec.rb similarity index 95% rename from spec/lib/personas/tools/tool_spec.rb rename to spec/lib/agents/tools/tool_spec.rb index 5896d8e3a..ce446ff14 100644 --- a/spec/lib/personas/tools/tool_spec.rb +++ b/spec/lib/agents/tools/tool_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::Personas::Tools::Tool do +RSpec.describe DiscourseAi::Agents::Tools::Tool do let :tool_class do described_class end diff --git a/spec/lib/personas/tools/update_artifact_spec.rb b/spec/lib/agents/tools/update_artifact_spec.rb similarity index 90% rename from spec/lib/personas/tools/update_artifact_spec.rb rename to spec/lib/agents/tools/update_artifact_spec.rb index 3f8b7f066..c8b25379e 100644 --- a/spec/lib/personas/tools/update_artifact_spec.rb +++ b/spec/lib/agents/tools/update_artifact_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::Personas::Tools::UpdateArtifact do +RSpec.describe DiscourseAi::Agents::Tools::UpdateArtifact do fab!(:llm_model) let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) } fab!(:post) @@ -44,10 +44,10 @@ }, bot_user: bot_user, llm: llm_model.to_llm, - persona_options: { + agent_options: { "update_algorithm" => "full", }, - context: DiscourseAi::Personas::BotContext.new(messages: [], post: post), + context: DiscourseAi::Agents::BotContext.new(messages: [], post: post), ) result = tool.invoke {} @@ -88,10 +88,10 @@ { artifact_id: artifact.id, instructions: "Update only JavaScript" }, bot_user: bot_user, llm: llm_model.to_llm, - persona_options: { + agent_options: { "update_algorithm" => "full", }, - context: DiscourseAi::Personas::BotContext.new(messages: [], post: post), + context: DiscourseAi::Agents::BotContext.new(messages: [], post: post), ) result = tool.invoke {} @@ -115,7 +115,7 @@ { artifact_id: artifact.id, instructions: "Invalid update" }, bot_user: bot_user, llm: llm_model.to_llm, - context: DiscourseAi::Personas::BotContext.new(messages: [], post: post), + context: DiscourseAi::Agents::BotContext.new(messages: [], post: post), ) result = tool.invoke {} @@ -129,7 +129,7 @@ { artifact_id: -1, instructions: "Update something" }, bot_user: bot_user, llm: llm_model.to_llm, - context: DiscourseAi::Personas::BotContext.new(messages: [], post: post), + context: DiscourseAi::Agents::BotContext.new(messages: [], post: post), ) result = tool.invoke {} @@ -152,10 +152,10 @@ { artifact_id: artifact.id, instructions: "Just update the HTML" }, bot_user: bot_user, llm: llm_model.to_llm, - persona_options: { + agent_options: { "update_algorithm" => "full", }, - context: DiscourseAi::Personas::BotContext.new(messages: [], post: post), + context: DiscourseAi::Agents::BotContext.new(messages: [], post: post), ) tool.invoke {} @@ -183,10 +183,10 @@ { artifact_id: artifact.id, instructions: "Update to version 1" }, bot_user: bot_user, llm: llm_model.to_llm, - persona_options: { + agent_options: { "update_algorithm" => "full", }, - context: DiscourseAi::Personas::BotContext.new(messages: [], post: post), + context: DiscourseAi::Agents::BotContext.new(messages: [], post: post), ) .invoke {} end @@ -209,10 +209,10 @@ }, bot_user: bot_user, llm: llm_model.to_llm, - persona_options: { + agent_options: { "update_algorithm" => "full", }, - context: DiscourseAi::Personas::BotContext.new(messages: [], post: post), + context: DiscourseAi::Agents::BotContext.new(messages: [], post: post), ) result = tool.invoke {} @@ -262,8 +262,8 @@ { artifact_id: artifact.id, instructions: "Change the text to Updated and color to red" }, bot_user: bot_user, llm: llm_model.to_llm, - context: DiscourseAi::Personas::BotContext.new(messages: [], post: post), - persona_options: { + context: DiscourseAi::Agents::BotContext.new(messages: [], post: post), + agent_options: { "update_algorithm" => "diff", }, ) @@ -330,8 +330,8 @@ { artifact_id: artifact.id, instructions: "Change the text to Updated and color to red" }, bot_user: bot_user, llm: llm_model.to_llm, - context: DiscourseAi::Personas::BotContext.new(messages: [], post: post), - persona_options: { + context: DiscourseAi::Agents::BotContext.new(messages: [], post: post), + agent_options: { "update_algorithm" => "diff", }, ) diff --git a/spec/lib/personas/tools/web_browser_spec.rb b/spec/lib/agents/tools/web_browser_spec.rb similarity index 98% rename from spec/lib/personas/tools/web_browser_spec.rb rename to spec/lib/agents/tools/web_browser_spec.rb index aebd4e66d..15d36bf7d 100644 --- a/spec/lib/personas/tools/web_browser_spec.rb +++ b/spec/lib/agents/tools/web_browser_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::Personas::Tools::WebBrowser do +RSpec.describe DiscourseAi::Agents::Tools::WebBrowser do fab!(:llm_model) let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) } let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } diff --git a/spec/lib/discord/bot/agent_replier_spec.rb b/spec/lib/discord/bot/agent_replier_spec.rb new file mode 100644 index 000000000..792201bcc --- /dev/null +++ b/spec/lib/discord/bot/agent_replier_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe DiscourseAi::Discord::Bot::AgentReplier do + let(:interaction_body) do + { data: { options: [{ value: "test query" }] }, token: "interaction_token" }.to_json.to_s + end + let(:agent_replier) { described_class.new(interaction_body) } + + fab!(:llm_model) + fab!(:agent) { Fabricate(:ai_agent, default_llm_id: llm_model.id) } + + before do + SiteSetting.ai_discord_search_agent = agent.id.to_s + allow_any_instance_of(DiscourseAi::Agents::Bot).to receive(:reply).and_return( + "This is a reply from bot!", + ) + allow(agent_replier).to receive(:create_reply) + end + + describe "#handle_interaction!" do + it "creates and updates replies" do + agent_replier.handle_interaction! + expect(agent_replier).to have_received(:create_reply).at_least(:once) + end + end +end diff --git a/spec/lib/discord/bot/persona_replier_spec.rb b/spec/lib/discord/bot/persona_replier_spec.rb deleted file mode 100644 index 9228e1bf1..000000000 --- a/spec/lib/discord/bot/persona_replier_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -RSpec.describe DiscourseAi::Discord::Bot::PersonaReplier do - let(:interaction_body) do - { data: { options: [{ value: "test query" }] }, token: "interaction_token" }.to_json.to_s - end - let(:persona_replier) { described_class.new(interaction_body) } - - fab!(:llm_model) - fab!(:persona) { Fabricate(:ai_persona, default_llm_id: llm_model.id) } - - before do - SiteSetting.ai_discord_search_persona = persona.id.to_s - allow_any_instance_of(DiscourseAi::Personas::Bot).to receive(:reply).and_return( - "This is a reply from bot!", - ) - allow(persona_replier).to receive(:create_reply) - end - - describe "#handle_interaction!" do - it "creates and updates replies" do - persona_replier.handle_interaction! - expect(persona_replier).to have_received(:create_reply).at_least(:once) - end - end -end diff --git a/spec/lib/discord/bot/search_spec.rb b/spec/lib/discord/bot/search_spec.rb index 36233f608..e8bf7bfa3 100644 --- a/spec/lib/discord/bot/search_spec.rb +++ b/spec/lib/discord/bot/search_spec.rb @@ -20,7 +20,7 @@ describe "#handle_interaction!" do it "creates a reply with search results" do - allow_any_instance_of(DiscourseAi::Personas::Tools::Search).to receive(:invoke).and_return( + allow_any_instance_of(DiscourseAi::Agents::Tools::Search).to receive(:invoke).and_return( { rows: [%w[Title /link]] }, ) search.handle_interaction! diff --git a/spec/lib/discourse_automation/llm_persona_triage_spec.rb b/spec/lib/discourse_automation/llm_agent_triage_spec.rb similarity index 92% rename from spec/lib/discourse_automation/llm_persona_triage_spec.rb rename to spec/lib/discourse_automation/llm_agent_triage_spec.rb index 8ea2123ec..9a5095a06 100644 --- a/spec/lib/discourse_automation/llm_persona_triage_spec.rb +++ b/spec/lib/discourse_automation/llm_agent_triage_spec.rb @@ -2,29 +2,29 @@ return if !defined?(DiscourseAutomation) -describe DiscourseAi::Automation::LlmPersonaTriage do +describe DiscourseAi::Automation::LlmAgentTriage do fab!(:user) fab!(:bot_user) { Fabricate(:user) } fab!(:llm_model) { Fabricate(:anthropic_model, name: "claude-3-opus", enabled_chat_bot: true) } - fab!(:ai_persona) do - persona = + fab!(:ai_agent) do + agent = Fabricate( - :ai_persona, + :ai_agent, name: "Triage Helper", - description: "A persona that helps with triaging posts", + description: "A agent that helps with triaging posts", system_prompt: "You are a helpful assistant that triages posts", default_llm: llm_model, ) - # Create the user for this persona - persona.update!(user_id: bot_user.id) - persona + # Create the user for this agent + agent.update!(user_id: bot_user.id) + agent end let(:automation) do - Fabricate(:automation, name: "my automation", script: "llm_persona_triage", enabled: true) + Fabricate(:automation, name: "my automation", script: "llm_agent_triage", enabled: true) end def add_automation_field(name, value, type: "text") @@ -42,11 +42,11 @@ def add_automation_field(name, value, type: "text") SiteSetting.ai_bot_enabled = true SiteSetting.ai_bot_allowed_groups = "#{Group::AUTO_GROUPS[:trust_level_0]}" - add_automation_field("persona", ai_persona.id, type: "choices") + add_automation_field("agent", ai_agent.id, type: "choices") add_automation_field("whisper", false, type: "boolean") end - it "can respond to a post using the specified persona" do + it "can respond to a post using the specified agent" do post = Fabricate(:post, raw: "This is a test post that needs triage") response_text = "I analyzed your post and can help with that." @@ -89,7 +89,7 @@ def add_automation_field(name, value, type: "text") expect(topic.posts.count).to eq(2) - # Verify that the response was posted by the persona's user + # Verify that the response was posted by the agent's user expect(last_post.user_id).to eq(bot_user.id) expect(last_post.raw).to eq(response_text) expect(last_post.post_type).to eq(Post.types[:regular]) # Not a whisper @@ -133,7 +133,7 @@ def add_automation_field(name, value, type: "text") post = Fabricate(:post, raw: "Error-triggering post") # Set up to cause an error - ai_persona.update!(user_id: nil) + ai_agent.update!(user_id: nil) # Should not raise an error expect { @@ -264,11 +264,11 @@ def add_automation_field(name, value, type: "text") topic = reply.topic - # should not inject persona into allowed users + # should not inject agent into allowed users expect(topic.topic_allowed_users.pluck(:user_id).sort).to eq(original_user_ids.sort) end - describe "LLM Persona Triage with Chat Message Creation" do + describe "LLM Agent Triage with Chat Message Creation" do fab!(:user) fab!(:bot_user) { Fabricate(:user) } fab!(:chat_channel) { Fabricate(:category_channel) } @@ -309,14 +309,14 @@ def add_automation_field(name, value, type: "text") before do SiteSetting.chat_enabled = true - ai_persona.update!(tools: ["custom-#{custom_tool.id}"]) + ai_agent.update!(tools: ["custom-#{custom_tool.id}"]) # Set up automation fields automation.fields.create!( component: "choices", - name: "persona", + name: "agent", metadata: { - value: ai_persona.id, + value: ai_agent.id, }, target: "script", ) diff --git a/spec/lib/discourse_automation/llm_tool_triage_spec.rb b/spec/lib/discourse_automation/llm_tool_triage_spec.rb index 8baa8dd02..0df43787f 100644 --- a/spec/lib/discourse_automation/llm_tool_triage_spec.rb +++ b/spec/lib/discourse_automation/llm_tool_triage_spec.rb @@ -8,10 +8,10 @@ fab!(:topic) { Fabricate(:topic, user: new_user) } fab!(:post) { Fabricate(:post, topic: topic, user: new_user, raw: "How do I reset my password?") } fab!(:llm_model) - fab!(:ai_persona) do - persona = Fabricate(:ai_persona, default_llm: llm_model) - persona.create_user - persona + fab!(:ai_agent) do + agent = Fabricate(:ai_agent, default_llm: llm_model) + agent.create_user + agent end fab!(:tool) do @@ -28,7 +28,7 @@ }; } - const helper = discourse.getPersona("#{ai_persona.name}"); + const helper = discourse.getAgent("#{ai_agent.name}"); const answer = helper.respondTo({ post_id: post.id }); return { @@ -79,7 +79,7 @@ const postId = context.post_id; const post = discourse.getPost(postId); - const helper = discourse.getPersona("#{ai_persona.name}"); + const helper = discourse.getAgent("#{ai_agent.name}"); // Pass instructions to make response a whisper const answer = helper.respondTo({ post_id: post.id, diff --git a/spec/lib/discourse_automation/llm_triage_spec.rb b/spec/lib/discourse_automation/llm_triage_spec.rb index 1b3ca6904..af92bb63d 100644 --- a/spec/lib/discourse_automation/llm_triage_spec.rb +++ b/spec/lib/discourse_automation/llm_triage_spec.rb @@ -5,7 +5,7 @@ describe DiscourseAi::Automation::LlmTriage do fab!(:category) fab!(:reply_user) { Fabricate(:user) } - fab!(:personal_message) { Fabricate(:private_message_topic) } + fab!(:agentl_message) { Fabricate(:private_message_topic) } let(:canned_reply_text) { "Hello, this is a reply" } let(:automation) { Fabricate(:automation, script: "llm_triage", enabled: true) } @@ -82,7 +82,7 @@ def add_automation_field(name, value, type: "text") end it "does not triage PMs by default" do - post = Fabricate(:post, topic: personal_message) + post = Fabricate(:post, topic: agentl_message) automation.running_in_background! automation.trigger!({ "post" => post }) @@ -93,9 +93,9 @@ def add_automation_field(name, value, type: "text") # needs to be admin or it will not be able to just step in to # PM reply_user.update!(admin: true) - add_automation_field("include_personal_messages", true, type: :boolean) + add_automation_field("include_agentl_messages", true, type: :boolean) add_automation_field("temperature", "0.2") - post = Fabricate(:post, topic: personal_message) + post = Fabricate(:post, topic: agentl_message) prompt_options = nil DiscourseAi::Completions::Llm.with_prepared_responses( @@ -124,11 +124,11 @@ def add_automation_field(name, value, type: "text") expect(last_post.raw).to eq post.raw end - it "can respond using an AI persona when configured" do + it "can respond using an AI agent when configured" do bot_user = Fabricate(:user, username: "ai_assistant") - ai_persona = + ai_agent = Fabricate( - :ai_persona, + :ai_agent, name: "Help Bot", description: "AI assistant for forum help", system_prompt: "You are a helpful forum assistant", @@ -136,16 +136,16 @@ def add_automation_field(name, value, type: "text") user_id: bot_user.id, ) - # Configure the automation to use the persona instead of canned reply + # Configure the automation to use the agent instead of canned reply add_automation_field("canned_reply", nil, type: "message") # Clear canned reply - add_automation_field("reply_persona", ai_persona.id, type: "choices") + add_automation_field("reply_agent", ai_agent.id, type: "choices") add_automation_field("whisper", true, type: "boolean") post = Fabricate(:post, raw: "I need help with a problem") ai_response = "I'll help you with your problem!" - # Set up the test to provide both the triage and the persona responses + # Set up the test to provide both the triage and the agent responses DiscourseAi::Completions::Llm.with_prepared_responses(["bad", ai_response]) do automation.running_in_background! automation.trigger!({ "post" => post }) @@ -155,7 +155,7 @@ def add_automation_field(name, value, type: "text") topic = post.topic.reload last_post = topic.posts.order(:post_number).last - # Verify the AI persona's user created the post + # Verify the AI agent's user created the post expect(last_post.user_id).to eq(bot_user.id) # Verify the content matches the AI response @@ -166,11 +166,11 @@ def add_automation_field(name, value, type: "text") end it "does not create replies when the action is edit" do - # Set up bot user and persona + # Set up bot user and agent bot_user = Fabricate(:user, username: "helper_bot") - ai_persona = + ai_agent = Fabricate( - :ai_persona, + :ai_agent, name: "Edit Helper", description: "AI assistant for editing", system_prompt: "You help with editing", @@ -180,7 +180,7 @@ def add_automation_field(name, value, type: "text") # Configure the automation with both reply methods add_automation_field("canned_reply", "This is a canned reply", type: "message") - add_automation_field("reply_persona", ai_persona.id, type: "choices") + add_automation_field("reply_agent", ai_agent.id, type: "choices") # Create a post and capture its topic post = Fabricate(:post, raw: "This needs to be evaluated") diff --git a/spec/lib/guardian_extensions_spec.rb b/spec/lib/guardian_extensions_spec.rb index 33d43c456..859ce2dd6 100644 --- a/spec/lib/guardian_extensions_spec.rb +++ b/spec/lib/guardian_extensions_spec.rb @@ -17,7 +17,7 @@ describe "#can_see_summary?" do context "when the user cannot generate a summary" do - before { assign_persona_to(:ai_summarization_persona, []) } + before { assign_agent_to(:ai_summarization_agent, []) } it "returns false" do expect(guardian.can_see_summary?(topic)).to eq(false) @@ -31,7 +31,7 @@ end context "when the user can generate a summary" do - before { assign_persona_to(:ai_summarization_persona, [group.id]) } + before { assign_agent_to(:ai_summarization_agent, [group.id]) } it "returns true if the user group is present in the ai_custom_summarization_allowed_groups_map setting" do expect(guardian.can_see_summary?(topic)).to eq(true) @@ -39,7 +39,7 @@ end context "when the topic is a PM" do - before { assign_persona_to(:ai_summarization_persona, [group.id]) } + before { assign_agent_to(:ai_summarization_agent, [group.id]) } let(:pm) { Fabricate(:private_message_topic) } it "returns false" do @@ -66,7 +66,7 @@ end describe "#can_see_gists?" do - before { assign_persona_to(:ai_summary_gists_persona, [group.id]) } + before { assign_agent_to(:ai_summary_gists_agent, [group.id]) } let(:guardian) { Guardian.new(user) } context "when access is restricted to the user's group" do @@ -86,7 +86,7 @@ end context "when access is set to everyone" do - before { assign_persona_to(:ai_summary_gists_persona, [Group::AUTO_GROUPS[:everyone]]) } + before { assign_agent_to(:ai_summary_gists_agent, [Group::AUTO_GROUPS[:everyone]]) } it "returns true" do expect(guardian.can_see_gists?).to eq(true) diff --git a/spec/lib/modules/ai_bot/entry_point_spec.rb b/spec/lib/modules/ai_bot/entry_point_spec.rb index 0e1aac3e2..0d32161b4 100644 --- a/spec/lib/modules/ai_bot/entry_point_spec.rb +++ b/spec/lib/modules/ai_bot/entry_point_spec.rb @@ -52,39 +52,39 @@ it "adds information about forcing default llm to current_user_serializer" do Group.refresh_automatic_groups! - persona = + agent = Fabricate( - :ai_persona, + :ai_agent, enabled: true, allowed_group_ids: [bot_allowed_group.id], default_llm_id: claude_2.id, force_default_llm: true, ) - persona.create_user! + agent.create_user! serializer = CurrentUserSerializer.new(admin, scope: Guardian.new(admin)) serializer = serializer.as_json bots = serializer[:current_user][:ai_enabled_chat_bots] - persona_bot = bots.find { |bot| bot["id"] == persona.user_id } + agent_bot = bots.find { |bot| bot["id"] == agent.user_id } - expect(persona_bot["username"]).to eq(persona.user.username) - expect(persona_bot["force_default_llm"]).to eq(true) + expect(agent_bot["username"]).to eq(agent.user.username) + expect(agent_bot["force_default_llm"]).to eq(true) end - it "includes user ids for all personas in the serializer" do + it "includes user ids for all agents in the serializer" do Group.refresh_automatic_groups! - persona = Fabricate(:ai_persona, enabled: true, allowed_group_ids: [bot_allowed_group.id]) - persona.create_user! + agent = Fabricate(:ai_agent, enabled: true, allowed_group_ids: [bot_allowed_group.id]) + agent.create_user! serializer = CurrentUserSerializer.new(admin, scope: Guardian.new(admin)) serializer = serializer.as_json bots = serializer[:current_user][:ai_enabled_chat_bots] - persona_bot = bots.find { |bot| bot["id"] == persona.user_id } - expect(persona_bot["username"]).to eq(persona.user.username) - expect(persona_bot["force_default_llm"]).to eq(false) + agent_bot = bots.find { |bot| bot["id"] == agent.user_id } + expect(agent_bot["username"]).to eq(agent.user.username) + expect(agent_bot["force_default_llm"]).to eq(false) end it "queues a job to generate a reply by the AI" do @@ -176,9 +176,9 @@ end end - it "will include ai_search_discoveries field in the user_option if discover persona is enabled" do + it "will include ai_search_discoveries field in the user_option if discover agent is enabled" do SiteSetting.ai_bot_enabled = true - SiteSetting.ai_bot_discover_persona = Fabricate(:ai_persona).id + SiteSetting.ai_bot_discover_agent = Fabricate(:ai_agent).id serializer = CurrentUserSerializer.new(Fabricate(:user), scope: Guardian.new(Fabricate(:user))) diff --git a/spec/lib/modules/ai_bot/jobs/regular/create_ai_reply_spec.rb b/spec/lib/modules/ai_bot/jobs/regular/create_ai_reply_spec.rb index 7808f1f9b..469252514 100644 --- a/spec/lib/modules/ai_bot/jobs/regular/create_ai_reply_spec.rb +++ b/spec/lib/modules/ai_bot/jobs/regular/create_ai_reply_spec.rb @@ -15,17 +15,17 @@ "Hello this is a bot and what you just said is an interesting question" end - before { SiteSetting.min_personal_message_post_length = 5 } + before { SiteSetting.min_agentl_message_post_length = 5 } it "adds a reply from the bot" do - persona_id = AiPersona.find_by(name: "Forum Helper").id + agent_id = AiAgent.find_by(name: "Forum Helper").id bot_user = DiscourseAi::AiBot::EntryPoint.find_user_from_model("gpt-3.5-turbo") DiscourseAi::Completions::Llm.with_prepared_responses([expected_response]) do subject.execute( post_id: topic.first_post.id, bot_user_id: bot_user.id, - persona_id: persona_id, + agent_id: agent_id, ) end diff --git a/spec/lib/modules/ai_bot/playground_spec.rb b/spec/lib/modules/ai_bot/playground_spec.rb index e9ad0ac35..50746593e 100644 --- a/spec/lib/modules/ai_bot/playground_spec.rb +++ b/spec/lib/modules/ai_bot/playground_spec.rb @@ -20,12 +20,12 @@ end fab!(:bot) do - persona = - AiPersona - .find(DiscourseAi::Personas::Persona.system_personas[DiscourseAi::Personas::General]) + agent = + AiAgent + .find(DiscourseAi::Agents::Agent.system_agents[DiscourseAi::Agents::General]) .class_instance .new - DiscourseAi::Personas::Bot.as(bot_user, persona: persona) + DiscourseAi::Agents::Bot.as(bot_user, agent: agent) end fab!(:admin) { Fabricate(:admin, refresh_auto_groups: true) } @@ -61,16 +61,16 @@ before { SiteSetting.ai_embeddings_enabled = false } after do - # we must reset cache on persona cause data can be rolled back - AiPersona.persona_cache.flush! + # we must reset cache on agent cause data can be rolled back + AiAgent.agent_cache.flush! end describe "is_bot_user_id?" do it "properly detects ALL bots as bot users" do - persona = Fabricate(:ai_persona, enabled: false) - persona.create_user! + agent = Fabricate(:ai_agent, enabled: false) + agent.create_user! - expect(DiscourseAi::AiBot::Playground.is_bot_user_id?(persona.user_id)).to eq(true) + expect(DiscourseAi::AiBot::Playground.is_bot_user_id?(agent.user_id)).to eq(true) end end @@ -88,7 +88,7 @@ ) end - let!(:ai_persona) { Fabricate(:ai_persona, tools: ["custom-#{custom_tool.id}"]) } + let!(:ai_agent) { Fabricate(:ai_agent, tools: ["custom-#{custom_tool.id}"]) } let(:tool_call) do DiscourseAi::Completions::ToolCall.new( name: "search", @@ -99,7 +99,7 @@ ) end - let(:bot) { DiscourseAi::Personas::Bot.as(bot_user, persona: ai_persona.class_instance.new) } + let(:bot) { DiscourseAi::Agents::Bot.as(bot_user, agent: ai_agent.class_instance.new) } let(:playground) { DiscourseAi::AiBot::Playground.new(bot) } @@ -114,7 +114,7 @@ JS tool_name = "custom-#{custom_tool.id}" - ai_persona.update!(tools: [[tool_name, nil, true]], tool_details: false) + ai_agent.update!(tools: [[tool_name, nil, true]], tool_details: false) reply_post = nil prompts = nil @@ -136,7 +136,7 @@ it "can force usage of a tool" do tool_name = "custom-#{custom_tool.id}" - ai_persona.update!(tools: [[tool_name, nil, true]], forced_tool_count: 1) + ai_agent.update!(tools: [[tool_name, nil, true]], forced_tool_count: 1) responses = [tool_call, ["custom tool did stuff (maybe)"], ["new PM title"]] prompts = nil @@ -154,7 +154,7 @@ expect(prompts[0].tool_choice).to eq("search") expect(prompts[1].tool_choice).to eq(nil) - ai_persona.update!(forced_tool_count: 1) + ai_agent.update!(forced_tool_count: 1) responses = ["no tool call here"] DiscourseAi::Completions::Llm.with_prepared_responses(responses) do |_, _, _prompts| @@ -168,8 +168,8 @@ end it "uses custom tool in conversation" do - persona_klass = AiPersona.all_personas.find { |p| p.name == ai_persona.name } - bot = DiscourseAi::Personas::Bot.as(bot_user, persona: persona_klass.new) + agent_klass = AiAgent.all_agents.find { |p| p.name == ai_agent.name } + bot = DiscourseAi::Agents::Bot.as(bot_user, agent: agent_klass.new) playground = described_class.new(bot) responses = [tool_call, "custom tool did stuff (maybe)"] @@ -208,8 +208,8 @@ custom_tool.update!(enabled: false) # so we pick up new cache - persona_klass = AiPersona.all_personas.find { |p| p.name == ai_persona.name } - bot = DiscourseAi::Personas::Bot.as(bot_user, persona: persona_klass.new) + agent_klass = AiAgent.all_agents.find { |p| p.name == ai_agent.name } + bot = DiscourseAi::Agents::Bot.as(bot_user, agent: agent_klass.new) playground = DiscourseAi::AiBot::Playground.new(bot) responses = ["custom tool did stuff (maybe)", tool_call] @@ -230,10 +230,10 @@ SiteSetting.ai_bot_allowed_groups = "#{Group::AUTO_GROUPS[:trust_level_0]}" end - fab!(:persona) do - AiPersona.create!( - name: "Test Persona", - description: "A test persona", + fab!(:agent) do + AiAgent.create!( + name: "Test Agent", + description: "A test agent", allowed_group_ids: [Group::AUTO_GROUPS[:trust_level_0]], enabled: true, system_prompt: "You are a helpful bot", @@ -249,10 +249,10 @@ it "sends images to llm" do post = nil - persona.create_user! + agent.create_user! image = "" - body = "Hey @#{persona.user.username}, can you help me with this image? #{image}" + body = "Hey @#{agent.user.username}, can you help me with this image? #{image}" prompts = nil DiscourseAi::Completions::Llm.with_prepared_responses( @@ -276,29 +276,29 @@ end end - describe "persona with user support" do + describe "agent with user support" do before do Jobs.run_immediately! SiteSetting.ai_bot_allowed_groups = "#{Group::AUTO_GROUPS[:trust_level_0]}" end - fab!(:persona) do - persona = - AiPersona.create!( - name: "Test Persona", - description: "A test persona", + fab!(:agent) do + agent = + AiAgent.create!( + name: "Test Agent", + description: "A test agent", allowed_group_ids: [Group::AUTO_GROUPS[:trust_level_0]], enabled: true, system_prompt: "You are a helpful bot", ) - persona.create_user! - persona.update!( + agent.create_user! + agent.update!( default_llm_id: claude_2.id, allow_chat_channel_mentions: true, allow_topic_mentions: true, ) - persona + agent end context "with chat channels" do @@ -314,7 +314,7 @@ SiteSetting.ai_bot_enabled = true SiteSetting.chat_allowed_groups = "#{Group::AUTO_GROUPS[:trust_level_0]}" Group.refresh_automatic_groups! - persona.update!(allow_chat_channel_mentions: true, default_llm_id: opus_model.id) + agent.update!(allow_chat_channel_mentions: true, default_llm_id: opus_model.id) end it "should behave in a sane way when threading is enabled" do @@ -358,7 +358,7 @@ message = ChatSDK::Message.create( channel_id: channel.id, - raw: "Hello @#{persona.user.username}", + raw: "Hello @#{agent.user.username}", guardian: guardian, ) @@ -418,7 +418,7 @@ ) do |_, _, _prompts| ChatSDK::Message.create( channel_id: channel.id, - raw: "Hello @#{persona.user.username}", + raw: "Hello @#{agent.user.username}", guardian: guardian, ) @@ -438,12 +438,12 @@ end context "with chat dms" do - fab!(:dm_channel) { Fabricate(:direct_message_channel, users: [user, persona.user]) } + fab!(:dm_channel) { Fabricate(:direct_message_channel, users: [user, agent.user]) } before do SiteSetting.chat_allowed_groups = "#{Group::AUTO_GROUPS[:trust_level_0]}" Group.refresh_automatic_groups! - persona.update!( + agent.update!( allow_chat_direct_messages: true, allow_topic_mentions: false, allow_chat_channel_mentions: false, @@ -479,7 +479,7 @@ end it "can run tools" do - persona.update!(tools: ["Time"]) + agent.update!(tools: ["Time"]) tool_call1 = DiscourseAi::Completions::ToolCall.new( @@ -535,8 +535,8 @@ expect(thread_messages.last.message).to eq("World") # it also needs to include history per config - first feed some history - persona.update!(enabled: false) - persona_guardian = Guardian.new(persona.user) + agent.update!(enabled: false) + agent_guardian = Guardian.new(agent.user) 4.times do |i| ChatSDK::Message.create( @@ -550,11 +550,11 @@ channel_id: dm_channel.id, thread_id: message.thread_id, raw: "response #{i}", - guardian: persona_guardian, + guardian: agent_guardian, ) end - persona.update!(max_context_posts: 4, enabled: true) + agent.update!(max_context_posts: 4, enabled: true) prompts = nil DiscourseAi::Completions::Llm.with_prepared_responses( @@ -599,7 +599,7 @@ post = create_post( title: "My public topic", - raw: "Hey @#{persona.user.username}, can you help me?", + raw: "Hey @#{agent.user.username}, can you help me?", post_type: Post.types[:whisper], ) end @@ -607,36 +607,36 @@ post.topic.reload last_post = post.topic.posts.order(:post_number).last expect(last_post.raw).to eq("Yes I can") - expect(last_post.user_id).to eq(persona.user_id) + expect(last_post.user_id).to eq(agent.user_id) expect(last_post.post_type).to eq(Post.types[:whisper]) end - it "allows mentioning a persona" do + it "allows mentioning a agent" do # we still should be able to mention with no bots toggle_enabled_bots(bots: []) - persona.update!(allow_topic_mentions: true) + agent.update!(allow_topic_mentions: true) post = nil DiscourseAi::Completions::Llm.with_prepared_responses(["Yes I can"]) do post = create_post( title: "My public topic", - raw: "Hey @#{persona.user.username}, can you help me?", + raw: "Hey @#{agent.user.username}, can you help me?", ) end post.topic.reload last_post = post.topic.posts.order(:post_number).last expect(last_post.raw).to eq("Yes I can") - expect(last_post.user_id).to eq(persona.user_id) + expect(last_post.user_id).to eq(agent.user_id) - persona.update!(allow_topic_mentions: false) + agent.update!(allow_topic_mentions: false) post = create_post( title: "My public topic ABC", - raw: "Hey @#{persona.user.username}, can you help me?", + raw: "Hey @#{agent.user.username}, can you help me?", ) expect(post.topic.posts.last.post_number).to eq(1) @@ -653,15 +653,15 @@ post = create_post( title: "I just made a PM", - raw: "Hey there #{persona.user.username}, can you help me?", - target_usernames: "#{user.username},#{persona.user.username},#{claude_2.user.username}", + raw: "Hey there #{agent.user.username}, can you help me?", + target_usernames: "#{user.username},#{agent.user.username},#{claude_2.user.username}", archetype: Archetype.private_message, user: admin, ) end # note that this is a string due to custom field shananigans - post.topic.custom_fields["ai_persona_id"] = persona.id.to_s + post.topic.custom_fields["ai_agent_id"] = agent.id.to_s post.topic.save_custom_fields llm2 = Fabricate(:llm_model, enabled_chat_bot: true) @@ -678,7 +678,7 @@ last_post = post.topic.reload.posts.order("id desc").first expect(last_post.raw).to eq("Hi from bot two") - expect(last_post.user_id).to eq(persona.user_id) + expect(last_post.user_id).to eq(agent.user_id) current_users = last_post.topic.reload.topic_allowed_users.joins(:user).pluck(:username) expect(current_users).to include(llm2.user.username) @@ -694,10 +694,10 @@ last_post = post.topic.reload.posts.order("id desc").first expect(last_post.raw).to eq("Hi from bot two") - expect(last_post.user_id).to eq(persona.user_id) + expect(last_post.user_id).to eq(agent.user_id) # tether llm, so it can no longer be switched - persona.update!(force_default_llm: true, default_llm_id: claude_2.id) + agent.update!(force_default_llm: true, default_llm_id: claude_2.id) DiscourseAi::Completions::Llm.with_prepared_responses(["Hi from bot one"], llm: claude_2) do create_post( @@ -709,10 +709,10 @@ last_post = post.topic.reload.posts.order("id desc").first expect(last_post.raw).to eq("Hi from bot one") - expect(last_post.user_id).to eq(persona.user_id) + expect(last_post.user_id).to eq(agent.user_id) end - it "allows PMing a persona even when no particular bots are enabled" do + it "allows PMing a agent even when no particular bots are enabled" do SiteSetting.ai_bot_enabled = true toggle_enabled_bots(bots: []) post = nil @@ -724,8 +724,8 @@ post = create_post( title: "I just made a PM", - raw: "Hey there #{persona.user.username}, can you help me?", - target_usernames: "#{user.username},#{persona.user.username}", + raw: "Hey there #{agent.user.username}, can you help me?", + target_usernames: "#{user.username},#{agent.user.username}", archetype: Archetype.private_message, user: admin, ) @@ -733,19 +733,19 @@ last_post = post.topic.posts.order(:post_number).last expect(last_post.raw).to eq("Yes I can") - expect(last_post.user_id).to eq(persona.user_id) + expect(last_post.user_id).to eq(agent.user_id) last_post.topic.reload - expect(last_post.topic.allowed_users.pluck(:user_id)).to include(persona.user_id) + expect(last_post.topic.allowed_users.pluck(:user_id)).to include(agent.user_id) expect(last_post.topic.participant_count).to eq(2) # ensure it can be disabled - persona.update!(allow_personal_messages: false) + agent.update!(allow_agentl_messages: false) post = create_post( - raw: "Hey there #{persona.user.username}, can you help me please", + raw: "Hey there #{agent.user.username}, can you help me please", topic_id: post.topic.id, user: admin, ) @@ -753,7 +753,7 @@ expect(post.post_number).to eq(3) end - it "can tether a persona unconditionally to an llm" do + it "can tether a agent unconditionally to an llm" do gpt_35_turbo = Fabricate(:llm_model, name: "gpt-3.5-turbo") # If you start a PM with GPT 3.5 bot, replies should come from it, not from Claude @@ -761,7 +761,7 @@ toggle_enabled_bots(bots: [gpt_35_turbo, claude_2]) post = nil - persona.update!(force_default_llm: true, default_llm_id: gpt_35_turbo.id) + agent.update!(force_default_llm: true, default_llm_id: gpt_35_turbo.id) DiscourseAi::Completions::Llm.with_prepared_responses( ["Yes I can", "Magic Title"], @@ -775,21 +775,21 @@ archetype: Archetype.private_message, user: admin, custom_fields: { - "ai_persona_id" => persona.id, + "ai_agent_id" => agent.id, }, ) end last_post = post.topic.posts.order(:post_number).last expect(last_post.raw).to eq("Yes I can") - expect(last_post.user_id).to eq(persona.user_id) + expect(last_post.user_id).to eq(agent.user_id) expect(last_post.custom_fields[DiscourseAi::AiBot::POST_AI_LLM_NAME_FIELD]).to eq( gpt_35_turbo.display_name, ) end - it "picks the correct llm for persona in PMs" do + it "picks the correct llm for agent in PMs" do gpt_35_turbo = Fabricate(:llm_model, name: "gpt-3.5-turbo") # If you start a PM with GPT 3.5 bot, replies should come from it, not from Claude @@ -809,7 +809,7 @@ post = create_post( title: "I just made a PM", - raw: "Hey @#{persona.user.username}, can you help me?", + raw: "Hey @#{agent.user.username}, can you help me?", target_usernames: "#{user.username},#{gpt3_5_bot_user.username}", archetype: Archetype.private_message, user: admin, @@ -823,10 +823,10 @@ expect(title_update_message.data).to eq({ title: "Magic Title" }) last_post = post.topic.posts.order(:post_number).last expect(last_post.raw).to eq("Yes I can") - expect(last_post.user_id).to eq(persona.user_id) + expect(last_post.user_id).to eq(agent.user_id) last_post.topic.reload - expect(last_post.topic.allowed_users.pluck(:user_id)).to include(persona.user_id) + expect(last_post.topic.allowed_users.pluck(:user_id)).to include(agent.user_id) # does not reply if replying directly to a user # nothing is mocked, so this would result in HTTP error @@ -838,7 +838,7 @@ reply_to_post_number: post.post_number, ) - # replies as correct persona if replying direct to persona + # replies as correct agent if replying direct to agent DiscourseAi::Completions::Llm.with_prepared_responses(["Another reply"], llm: gpt_35_turbo) do create_post( raw: "Please ignore this bot, I am replying to a user", @@ -850,14 +850,14 @@ last_post = post.topic.posts.order(:post_number).last expect(last_post.raw).to eq("Another reply") - expect(last_post.user_id).to eq(persona.user_id) + expect(last_post.user_id).to eq(agent.user_id) end end describe "#title_playground" do let(:expected_response) { "This is a suggested title" } - before { SiteSetting.min_personal_message_post_length = 5 } + before { SiteSetting.min_agentl_message_post_length = 5 } it "updates the title using bot suggestions" do DiscourseAi::Completions::Llm.with_prepared_responses([expected_response]) do @@ -985,8 +985,8 @@ end it "supports disabling tool details" do - persona = Fabricate(:ai_persona, tool_details: false, tools: ["Search"]) - bot = DiscourseAi::Personas::Bot.as(bot_user, persona: persona.class_instance.new) + agent = Fabricate(:ai_agent, tool_details: false, tools: ["Search"]) + bot = DiscourseAi::Agents::Bot.as(bot_user, agent: agent.class_instance.new) playground = described_class.new(bot) response1 = @@ -1037,13 +1037,13 @@ context "with Dall E bot" do before { SiteSetting.ai_openai_api_key = "123" } - let(:persona) do - AiPersona.find( - DiscourseAi::Personas::Persona.system_personas[DiscourseAi::Personas::DallE3], + let(:agent) do + AiAgent.find( + DiscourseAi::Agents::Agent.system_agents[DiscourseAi::Agents::DallE3], ) end - let(:bot) { DiscourseAi::Personas::Bot.as(bot_user, persona: persona.class_instance.new) } + let(:bot) { DiscourseAi::Agents::Bot.as(bot_user, agent: agent.class_instance.new) } let(:data) do image = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==" @@ -1062,7 +1062,7 @@ end it "properly returns an image when skipping tool details" do - persona.update!(tool_details: false) + agent.update!(tool_details: false) WebMock.stub_request(:post, SiteSetting.ai_openai_image_generation_url).to_return( status: 200, @@ -1165,11 +1165,11 @@ end describe "#available_bot_usernames" do - it "includes persona users" do - persona = Fabricate(:ai_persona) - persona.create_user! + it "includes agent users" do + agent = Fabricate(:ai_agent) + agent.create_user! - expect(playground.available_bot_usernames).to include(persona.user.username) + expect(playground.available_bot_usernames).to include(agent.user.username) end end @@ -1198,8 +1198,8 @@ ) end - let!(:ai_persona) { Fabricate(:ai_persona, tools: ["custom-#{custom_tool.id}"]) } - let(:bot) { DiscourseAi::Personas::Bot.as(bot_user, persona: ai_persona.class_instance.new) } + let!(:ai_agent) { Fabricate(:ai_agent, tools: ["custom-#{custom_tool.id}"]) } + let(:bot) { DiscourseAi::Agents::Bot.as(bot_user, agent: ai_agent.class_instance.new) } let(:playground) { DiscourseAi::AiBot::Playground.new(bot) } it "injects custom context into the prompt" do diff --git a/spec/lib/modules/sentiment/post_classification_spec.rb b/spec/lib/modules/sentiment/post_classification_spec.rb index f63da694e..8c8b8fcdb 100644 --- a/spec/lib/modules/sentiment/post_classification_spec.rb +++ b/spec/lib/modules/sentiment/post_classification_spec.rb @@ -127,7 +127,7 @@ def check_classification_for(post) end describe ".backfill_query" do - it "excludes posts in personal messages" do + it "excludes posts in agentl messages" do Fabricate(:private_message_post) posts = described_class.backfill_query diff --git a/spec/lib/modules/summarization/entry_point_spec.rb b/spec/lib/modules/summarization/entry_point_spec.rb index 0d57cbf9d..9eb80e859 100644 --- a/spec/lib/modules/summarization/entry_point_spec.rb +++ b/spec/lib/modules/summarization/entry_point_spec.rb @@ -63,7 +63,7 @@ before do group.add(user) - assign_persona_to(:ai_summary_gists_persona, [group.id]) + assign_agent_to(:ai_summary_gists_agent, [group.id]) SiteSetting.ai_summary_gists_enabled = true end diff --git a/spec/lib/utils/research/filter_spec.rb b/spec/lib/utils/research/filter_spec.rb index 866d63e81..2869e5ead 100644 --- a/spec/lib/utils/research/filter_spec.rb +++ b/spec/lib/utils/research/filter_spec.rb @@ -4,7 +4,7 @@ describe "integration tests" do before_all do SiteSetting.min_topic_title_length = 3 - SiteSetting.min_personal_message_title_length = 3 + SiteSetting.min_agentl_message_title_length = 3 end fab!(:user) diff --git a/spec/models/ai_agent_multisite_spec.rb b/spec/models/ai_agent_multisite_spec.rb new file mode 100644 index 000000000..a49033eb1 --- /dev/null +++ b/spec/models/ai_agent_multisite_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +RSpec.describe AiAgent, type: :multisite do + it "is able to amend settings on system agents on multisite" do + agent = AiAgent.find_by(name: "Designer") + expect(agent.allow_agentl_messages).to eq(true) + agent.update!(allow_agentl_messages: false) + + instance = agent.class_instance + expect(instance.allow_agentl_messages).to eq(false) + + test_multisite_connection("second") do + agent = AiAgent.find_by(name: "Designer") + expect(agent.allow_agentl_messages).to eq(true) + instance = agent.class_instance + expect(instance.name).to eq("Designer") + expect(instance.allow_agentl_messages).to eq(true) + end + + agent = AiAgent.find_by(name: "Designer") + instance = agent.class_instance + expect(instance.allow_agentl_messages).to eq(false) + end +end diff --git a/spec/models/ai_persona_spec.rb b/spec/models/ai_agent_spec.rb similarity index 55% rename from spec/models/ai_persona_spec.rb rename to spec/models/ai_agent_spec.rb index 0e6b9d13d..c10d1337a 100644 --- a/spec/models/ai_persona_spec.rb +++ b/spec/models/ai_agent_spec.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -RSpec.describe AiPersona do - subject(:basic_persona) do - AiPersona.new( +RSpec.describe AiAgent do + subject(:basic_agent) do + AiAgent.new( name: "test", description: "test", system_prompt: "test", @@ -15,74 +15,74 @@ fab!(:seeded_llm_model) { Fabricate(:llm_model, id: -1) } it "validates context settings" do - expect(basic_persona.valid?).to eq(true) + expect(basic_agent.valid?).to eq(true) - basic_persona.max_context_posts = 0 - expect(basic_persona.valid?).to eq(false) - expect(basic_persona.errors[:max_context_posts]).to eq(["must be greater than 0"]) + basic_agent.max_context_posts = 0 + expect(basic_agent.valid?).to eq(false) + expect(basic_agent.errors[:max_context_posts]).to eq(["must be greater than 0"]) - basic_persona.max_context_posts = 1 - expect(basic_persona.valid?).to eq(true) + basic_agent.max_context_posts = 1 + expect(basic_agent.valid?).to eq(true) - basic_persona.max_context_posts = nil - expect(basic_persona.valid?).to eq(true) + basic_agent.max_context_posts = nil + expect(basic_agent.valid?).to eq(true) end it "validates tools" do Fabricate(:ai_tool, id: 1) Fabricate(:ai_tool, id: 2, name: "Archie search", tool_name: "search") - expect(basic_persona.valid?).to eq(true) + expect(basic_agent.valid?).to eq(true) - basic_persona.tools = %w[search image_generation] - expect(basic_persona.valid?).to eq(true) + basic_agent.tools = %w[search image_generation] + expect(basic_agent.valid?).to eq(true) - basic_persona.tools = %w[search image_generation search] - expect(basic_persona.valid?).to eq(false) - expect(basic_persona.errors[:tools]).to eq(["Can not have duplicate tools"]) + basic_agent.tools = %w[search image_generation search] + expect(basic_agent.valid?).to eq(false) + expect(basic_agent.errors[:tools]).to eq(["Can not have duplicate tools"]) - basic_persona.tools = [ + basic_agent.tools = [ ["custom-1", { test: "test" }, false], ["custom-2", { test: "test" }, false], ] - expect(basic_persona.valid?).to eq(true) - expect(basic_persona.errors[:tools]).to eq([]) + expect(basic_agent.valid?).to eq(true) + expect(basic_agent.errors[:tools]).to eq([]) - basic_persona.tools = [ + basic_agent.tools = [ ["custom-1", { test: "test" }, false], ["custom-1", { test: "test" }, false], ] - expect(basic_persona.valid?).to eq(false) - expect(basic_persona.errors[:tools]).to eq(["Can not have duplicate tools"]) + expect(basic_agent.valid?).to eq(false) + expect(basic_agent.errors[:tools]).to eq(["Can not have duplicate tools"]) - basic_persona.tools = [ + basic_agent.tools = [ ["custom-1", { test: "test" }, false], ["custom-2", { test: "test" }, false], "image_generation", ] - expect(basic_persona.valid?).to eq(true) - expect(basic_persona.errors[:tools]).to eq([]) + expect(basic_agent.valid?).to eq(true) + expect(basic_agent.errors[:tools]).to eq([]) - basic_persona.tools = [ + basic_agent.tools = [ ["custom-1", { test: "test" }, false], ["custom-2", { test: "test" }, false], "Search", ] - expect(basic_persona.valid?).to eq(false) - expect(basic_persona.errors[:tools]).to eq(["Can not have duplicate tools"]) + expect(basic_agent.valid?).to eq(false) + expect(basic_agent.errors[:tools]).to eq(["Can not have duplicate tools"]) end it "allows creation of user" do - user = basic_persona.create_user! + user = basic_agent.create_user! expect(user.username).to eq("test_bot") expect(user.name).to eq("Test") expect(user.bot?).to be(true) - expect(user.id).to be <= AiPersona::FIRST_PERSONA_USER_ID + expect(user.id).to be <= AiAgent::FIRST_AGENT_USER_ID end it "removes all rag embeddings when rag params change" do - persona = - AiPersona.create!( + agent = + AiAgent.create!( name: "test", description: "test", system_prompt: "test", @@ -94,26 +94,26 @@ id = RagDocumentFragment.create!( - target: persona, + target: agent, fragment: "test", fragment_number: 1, upload: Fabricate(:upload), ).id - persona.rag_chunk_tokens = 20 - persona.save! + agent.rag_chunk_tokens = 20 + agent.save! expect(RagDocumentFragment.exists?(id)).to eq(false) end - it "defines singleton methods on system persona classes" do - forum_helper = AiPersona.find_by(name: "Forum Helper") + it "defines singleton methods on system agent classes" do + forum_helper = AiAgent.find_by(name: "Forum Helper") forum_helper.update!( user_id: 1, default_llm_id: llm_model.id, max_context_posts: 3, allow_topic_mentions: true, - allow_personal_messages: true, + allow_agentl_messages: true, allow_chat_channel_mentions: true, allow_chat_direct_messages: true, ) @@ -128,14 +128,14 @@ expect(klass.default_llm_id).to eq(llm_model.id) expect(klass.max_context_posts).to eq(3) expect(klass.allow_topic_mentions).to eq(true) - expect(klass.allow_personal_messages).to eq(true) + expect(klass.allow_agentl_messages).to eq(true) expect(klass.allow_chat_channel_mentions).to eq(true) expect(klass.allow_chat_direct_messages).to eq(true) end - it "defines singleton methods non persona classes" do - persona = - AiPersona.create!( + it "defines singleton methods non agent classes" do + agent = + AiAgent.create!( name: "test", description: "test", system_prompt: "test", @@ -144,29 +144,29 @@ default_llm_id: llm_model.id, max_context_posts: 3, allow_topic_mentions: true, - allow_personal_messages: true, + allow_agentl_messages: true, allow_chat_channel_mentions: true, allow_chat_direct_messages: true, user_id: 1, ) - klass = persona.class_instance + klass = agent.class_instance - expect(klass.id).to eq(persona.id) + expect(klass.id).to eq(agent.id) expect(klass.system).to eq(false) expect(klass.allowed_group_ids).to eq([]) expect(klass.user_id).to eq(1) expect(klass.default_llm_id).to eq(llm_model.id) expect(klass.max_context_posts).to eq(3) expect(klass.allow_topic_mentions).to eq(true) - expect(klass.allow_personal_messages).to eq(true) + expect(klass.allow_agentl_messages).to eq(true) expect(klass.allow_chat_channel_mentions).to eq(true) expect(klass.allow_chat_direct_messages).to eq(true) end it "does not allow setting allowing chat without a default_llm" do - persona = - AiPersona.create( + agent = + AiAgent.create( name: "test", description: "test", system_prompt: "test", @@ -175,13 +175,13 @@ allow_chat_channel_mentions: true, ) - expect(persona.valid?).to eq(false) - expect(persona.errors[:default_llm].first).to eq( - I18n.t("discourse_ai.ai_bot.personas.default_llm_required"), + expect(agent.valid?).to eq(false) + expect(agent.errors[:default_llm].first).to eq( + I18n.t("discourse_ai.ai_bot.agents.default_llm_required"), ) - persona = - AiPersona.create( + agent = + AiAgent.create( name: "test", description: "test", system_prompt: "test", @@ -190,13 +190,13 @@ allow_chat_direct_messages: true, ) - expect(persona.valid?).to eq(false) - expect(persona.errors[:default_llm].first).to eq( - I18n.t("discourse_ai.ai_bot.personas.default_llm_required"), + expect(agent.valid?).to eq(false) + expect(agent.errors[:default_llm].first).to eq( + I18n.t("discourse_ai.ai_bot.agents.default_llm_required"), ) - persona = - AiPersona.create( + agent = + AiAgent.create( name: "test", description: "test", system_prompt: "test", @@ -205,28 +205,28 @@ allow_topic_mentions: true, ) - expect(persona.valid?).to eq(false) - expect(persona.errors[:default_llm].first).to eq( - I18n.t("discourse_ai.ai_bot.personas.default_llm_required"), + expect(agent.valid?).to eq(false) + expect(agent.errors[:default_llm].first).to eq( + I18n.t("discourse_ai.ai_bot.agents.default_llm_required"), ) end it "validates allowed seeded model" do - basic_persona.default_llm_id = seeded_llm_model.id + basic_agent.default_llm_id = seeded_llm_model.id SiteSetting.ai_bot_allowed_seeded_models = "" - expect(basic_persona.valid?).to eq(false) - expect(basic_persona.errors[:default_llm]).to include( + expect(basic_agent.valid?).to eq(false) + expect(basic_agent.errors[:default_llm]).to include( I18n.t("discourse_ai.llm.configuration.invalid_seeded_model"), ) SiteSetting.ai_bot_allowed_seeded_models = "-1" - expect(basic_persona.valid?).to eq(true) + expect(basic_agent.valid?).to eq(true) end it "does not leak caches between sites" do - AiPersona.create!( + AiAgent.create!( name: "pun_bot", description: "you write puns", system_prompt: "you are pun bot", @@ -234,19 +234,19 @@ allowed_group_ids: [Group::AUTO_GROUPS[:trust_level_0]], ) - AiPersona.all_personas + AiAgent.all_agents - expect(AiPersona.persona_cache[:value].length).to be > (0) + expect(AiAgent.agent_cache[:value].length).to be > (0) RailsMultisite::ConnectionManagement.stubs(:current_db) { "abc" } - expect(AiPersona.persona_cache[:value]).to eq(nil) + expect(AiAgent.agent_cache[:value]).to eq(nil) end - describe "system persona validations" do - let(:system_persona) do - AiPersona.create!( - name: "system_persona", - description: "system persona", - system_prompt: "system persona", + describe "system agent validations" do + let(:system_agent) do + AiAgent.create!( + name: "system_agent", + description: "system agent", + system_prompt: "system agent", tools: %w[Search Time], response_format: [{ key: "summary", type: "string" }], examples: [%w[user_msg1 assistant_msg1], %w[user_msg2 assistant_msg2]], @@ -254,25 +254,25 @@ ) end - context "when modifying a system persona" do + context "when modifying a system agent" do it "allows changing tool options without allowing tool additions/removals" do tools = [["Search", { "base_query" => "abc" }], ["Time"]] - system_persona.update!(tools: tools) + system_agent.update!(tools: tools) - system_persona.reload - expect(system_persona.tools).to eq(tools) + system_agent.reload + expect(system_agent.tools).to eq(tools) invalid_tools = ["Time"] - system_persona.update(tools: invalid_tools) - expect(system_persona.errors[:base]).to include( - I18n.t("discourse_ai.ai_bot.personas.cannot_edit_system_persona"), + system_agent.update(tools: invalid_tools) + expect(system_agent.errors[:base]).to include( + I18n.t("discourse_ai.ai_bot.agents.cannot_edit_system_agent"), ) end it "doesn't accept response format changes" do new_format = [{ key: "summary2", type: "string" }] - expect { system_persona.update!(response_format: new_format) }.to raise_error( + expect { system_agent.update!(response_format: new_format) }.to raise_error( ActiveRecord::RecordInvalid, ) end @@ -280,7 +280,7 @@ it "doesn't accept additional format changes" do new_format = [{ key: "summary", type: "string" }, { key: "summary2", type: "string" }] - expect { system_persona.update!(response_format: new_format) }.to raise_error( + expect { system_agent.update!(response_format: new_format) }.to raise_error( ActiveRecord::RecordInvalid, ) end @@ -288,7 +288,7 @@ it "doesn't accept changes to examples" do other_examples = [%w[user_msg1 assistant_msg1]] - expect { system_persona.update!(examples: other_examples) }.to raise_error( + expect { system_agent.update!(examples: other_examples) }.to raise_error( ActiveRecord::RecordInvalid, ) end @@ -297,27 +297,27 @@ describe "validates examples format" do it "doesn't accept examples that are not arrays" do - basic_persona.examples = [1] + basic_agent.examples = [1] - expect(basic_persona.valid?).to eq(false) - expect(basic_persona.errors[:examples].first).to eq( - I18n.t("discourse_ai.personas.malformed_examples"), + expect(basic_agent.valid?).to eq(false) + expect(basic_agent.errors[:examples].first).to eq( + I18n.t("discourse_ai.agents.malformed_examples"), ) end it "doesn't accept examples that don't come in pairs" do - basic_persona.examples = [%w[user_msg1]] + basic_agent.examples = [%w[user_msg1]] - expect(basic_persona.valid?).to eq(false) - expect(basic_persona.errors[:examples].first).to eq( - I18n.t("discourse_ai.personas.malformed_examples"), + expect(basic_agent.valid?).to eq(false) + expect(basic_agent.errors[:examples].first).to eq( + I18n.t("discourse_ai.agents.malformed_examples"), ) end it "works when example is well formatted" do - basic_persona.examples = [%w[user_msg1 assistant1]] + basic_agent.examples = [%w[user_msg1 assistant1]] - expect(basic_persona.valid?).to eq(true) + expect(basic_agent.valid?).to eq(true) end end end diff --git a/spec/models/ai_persona_multisite_spec.rb b/spec/models/ai_persona_multisite_spec.rb deleted file mode 100644 index 846703035..000000000 --- a/spec/models/ai_persona_multisite_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe AiPersona, type: :multisite do - it "is able to amend settings on system personas on multisite" do - persona = AiPersona.find_by(name: "Designer") - expect(persona.allow_personal_messages).to eq(true) - persona.update!(allow_personal_messages: false) - - instance = persona.class_instance - expect(instance.allow_personal_messages).to eq(false) - - test_multisite_connection("second") do - persona = AiPersona.find_by(name: "Designer") - expect(persona.allow_personal_messages).to eq(true) - instance = persona.class_instance - expect(instance.name).to eq("Designer") - expect(instance.allow_personal_messages).to eq(true) - end - - persona = AiPersona.find_by(name: "Designer") - instance = persona.class_instance - expect(instance.allow_personal_messages).to eq(false) - end -end diff --git a/spec/models/ai_tool_spec.rb b/spec/models/ai_tool_spec.rb index 56de5de5c..a15bc9812 100644 --- a/spec/models/ai_tool_spec.rb +++ b/spec/models/ai_tool_spec.rb @@ -121,7 +121,7 @@ def create_tool( }, ) - expect { runner.invoke }.to raise_error(DiscourseAi::Personas::ToolRunner::TooManyRequestsError) + expect { runner.invoke }.to raise_error(DiscourseAi::Agents::ToolRunner::TooManyRequestsError) end it "can perform GET HTTP requests" do @@ -566,15 +566,15 @@ def stub_embeddings end end - context "when updating personas" do - fab!(:ai_persona) do - Fabricate(:ai_persona, name: "TestPersona", system_prompt: "Original prompt") + context "when updating agents" do + fab!(:ai_agent) do + Fabricate(:ai_agent, name: "TestAgent", system_prompt: "Original prompt") end - it "can update a persona with proper permissions" do + it "can update a agent with proper permissions" do script = <<~JS function invoke(params) { - return discourse.updatePersona(params.persona_name, { + return discourse.updateAgent(params.agent_name, { system_prompt: params.new_prompt, temperature: 0.7, top_p: 0.9 @@ -585,28 +585,28 @@ def stub_embeddings tool = create_tool(script: script) runner = tool.runner( - { persona_name: "TestPersona", new_prompt: "Updated system prompt" }, + { agent_name: "TestAgent", new_prompt: "Updated system prompt" }, llm: nil, bot_user: bot_user, ) result = runner.invoke expect(result["success"]).to eq(true) - expect(result["persona"]["system_prompt"]).to eq("Updated system prompt") - expect(result["persona"]["temperature"]).to eq(0.7) + expect(result["agent"]["system_prompt"]).to eq("Updated system prompt") + expect(result["agent"]["temperature"]).to eq(0.7) - ai_persona.reload - expect(ai_persona.system_prompt).to eq("Updated system prompt") - expect(ai_persona.temperature).to eq(0.7) - expect(ai_persona.top_p).to eq(0.9) + ai_agent.reload + expect(ai_agent.system_prompt).to eq("Updated system prompt") + expect(ai_agent.temperature).to eq(0.7) + expect(ai_agent.top_p).to eq(0.9) end end - context "when fetching persona information" do - fab!(:ai_persona) do + context "when fetching agent information" do + fab!(:ai_agent) do Fabricate( - :ai_persona, - name: "TestPersona", + :ai_agent, + name: "TestAgent", description: "Test description", system_prompt: "Test system prompt", temperature: 0.8, @@ -616,21 +616,21 @@ def stub_embeddings ) end - it "can fetch a persona by name" do + it "can fetch a agent by name" do script = <<~JS function invoke(params) { - const persona = discourse.getPersona(params.persona_name); - return persona; + const agent = discourse.getAgent(params.agent_name); + return agent; } JS tool = create_tool(script: script) - runner = tool.runner({ persona_name: "TestPersona" }, llm: nil, bot_user: bot_user) + runner = tool.runner({ agent_name: "TestAgent" }, llm: nil, bot_user: bot_user) result = runner.invoke - expect(result["id"]).to eq(ai_persona.id) - expect(result["name"]).to eq("TestPersona") + expect(result["id"]).to eq(ai_agent.id) + expect(result["name"]).to eq("TestAgent") expect(result["description"]).to eq("Test description") expect(result["system_prompt"]).to eq("Test system prompt") expect(result["temperature"]).to eq(0.8) @@ -640,25 +640,25 @@ def stub_embeddings expect(result["tools"][1]).to be_a(Array) end - it "raises an error when the persona doesn't exist" do + it "raises an error when the agent doesn't exist" do script = <<~JS function invoke(params) { - return discourse.getPersona("NonExistentPersona"); + return discourse.getAgent("NonExistentAgent"); } JS tool = create_tool(script: script) runner = tool.runner({}, llm: nil, bot_user: bot_user) - expect { runner.invoke }.to raise_error(MiniRacer::RuntimeError, /Persona not found/) + expect { runner.invoke }.to raise_error(MiniRacer::RuntimeError, /Agent not found/) end - it "can update a persona after fetching it" do + it "can update a agent after fetching it" do script = <<~JS function invoke(params) { - const persona = discourse.getPersona("TestPersona"); - return persona.update({ - system_prompt: "Updated through getPersona().update()", + const agent = discourse.getAgent("TestAgent"); + return agent.update({ + system_prompt: "Updated through getAgent().update()", temperature: 0.5 }); } @@ -670,9 +670,9 @@ def stub_embeddings result = runner.invoke expect(result["success"]).to eq(true) - ai_persona.reload - expect(ai_persona.system_prompt).to eq("Updated through getPersona().update()") - expect(ai_persona.temperature).to eq(0.5) + ai_agent.reload + expect(ai_agent.system_prompt).to eq("Updated through getAgent().update()") + expect(ai_agent.temperature).to eq(0.5) end end end diff --git a/spec/models/rag_document_fragment_spec.rb b/spec/models/rag_document_fragment_spec.rb index 58824c47f..5cdb8d2f8 100644 --- a/spec/models/rag_document_fragment_spec.rb +++ b/spec/models/rag_document_fragment_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.describe RagDocumentFragment do - fab!(:persona) { Fabricate(:ai_persona) } + fab!(:agent) { Fabricate(:ai_agent) } fab!(:upload_1) { Fabricate(:upload) } fab!(:upload_2) { Fabricate(:upload) } fab!(:vector_def) { Fabricate(:embedding_definition) } @@ -11,8 +11,8 @@ SiteSetting.ai_embeddings_enabled = true end - describe ".link_uploads_and_persona" do - it "does nothing if there is no persona" do + describe ".link_uploads_and_agent" do + it "does nothing if there is no agent" do expect { described_class.link_target_and_uploads(nil, [upload_1.id]) }.not_to change( Jobs::DigestRagUpload.jobs, :size, @@ -20,7 +20,7 @@ end it "does nothing if there are no uploads" do - expect { described_class.link_target_and_uploads(persona, []) }.not_to change( + expect { described_class.link_target_and_uploads(agent, []) }.not_to change( Jobs::DigestRagUpload.jobs, :size, ) @@ -28,21 +28,21 @@ it "queues a job for each upload to generate fragments" do expect { - described_class.link_target_and_uploads(persona, [upload_1.id, upload_2.id]) + described_class.link_target_and_uploads(agent, [upload_1.id, upload_2.id]) }.to change(Jobs::DigestRagUpload.jobs, :size).by(2) end - it "creates references between the persona an each upload" do - described_class.link_target_and_uploads(persona, [upload_1.id, upload_2.id]) + it "creates references between the agent an each upload" do + described_class.link_target_and_uploads(agent, [upload_1.id, upload_2.id]) - refs = UploadReference.where(target: persona).pluck(:upload_id) + refs = UploadReference.where(target: agent).pluck(:upload_id) expect(refs).to contain_exactly(upload_1.id, upload_2.id) end end describe ".update_target_uploads" do - it "does nothing if there is no persona" do + it "does nothing if there is no agent" do expect { described_class.update_target_uploads(nil, [upload_1.id]) }.not_to change( Jobs::DigestRagUpload.jobs, :size, @@ -50,24 +50,24 @@ end it "deletes the fragment if its not present in the uploads list" do - fragment = Fabricate(:rag_document_fragment, target: persona) + fragment = Fabricate(:rag_document_fragment, target: agent) - described_class.update_target_uploads(persona, []) + described_class.update_target_uploads(agent, []) expect { fragment.reload }.to raise_error(ActiveRecord::RecordNotFound) end - it "delete references between the upload and the persona" do - described_class.link_target_and_uploads(persona, [upload_1.id, upload_2.id]) - described_class.update_target_uploads(persona, [upload_2.id]) + it "delete references between the upload and the agent" do + described_class.link_target_and_uploads(agent, [upload_1.id, upload_2.id]) + described_class.update_target_uploads(agent, [upload_2.id]) - refs = UploadReference.where(target: persona).pluck(:upload_id) + refs = UploadReference.where(target: agent).pluck(:upload_id) expect(refs).to contain_exactly(upload_2.id) end it "queues jobs to generate new fragments" do - expect { described_class.update_target_uploads(persona, [upload_1.id]) }.to change( + expect { described_class.update_target_uploads(agent, [upload_1.id]) }.to change( Jobs::DigestRagUpload.jobs, :size, ).by(1) @@ -78,11 +78,11 @@ let(:vector) { DiscourseAi::Embeddings::Vector.instance } let(:rag_document_fragment_1) do - Fabricate(:rag_document_fragment, upload: upload_1, target: persona) + Fabricate(:rag_document_fragment, upload: upload_1, target: agent) end let(:rag_document_fragment_2) do - Fabricate(:rag_document_fragment, upload: upload_1, target: persona) + Fabricate(:rag_document_fragment, upload: upload_1, target: agent) end let(:expected_embedding) { [0.0038493] * vector_def.dimensions } @@ -103,8 +103,8 @@ it "regenerates all embeddings if ai_embeddings_selected_model changes" do old_id = rag_document_fragment_1.id - UploadReference.create!(upload_id: upload_1.id, target: persona) - UploadReference.create!(upload_id: upload_2.id, target: persona) + UploadReference.create!(upload_id: upload_1.id, target: agent) + UploadReference.create!(upload_id: upload_2.id, target: agent) Sidekiq::Testing.fake! do SiteSetting.ai_embeddings_selected_model = Fabricate(:open_ai_embedding_def).id @@ -114,7 +114,7 @@ end it "returns total, indexed and unindexed fragments for each upload" do - results = described_class.indexing_status(persona, [upload_1, upload_2]) + results = described_class.indexing_status(agent, [upload_1, upload_2]) upload_1_status = results[upload_1.id] expect(upload_1_status[:total]).to eq(2) diff --git a/spec/models/user_option_spec.rb b/spec/models/user_option_spec.rb index 34121ab93..7d40065f4 100644 --- a/spec/models/user_option_spec.rb +++ b/spec/models/user_option_spec.rb @@ -4,8 +4,8 @@ fab!(:user) fab!(:llm_model) fab!(:group) - fab!(:ai_persona) do - Fabricate(:ai_persona, allowed_group_ids: [group.id], default_llm_id: llm_model.id) + fab!(:ai_agent) do + Fabricate(:ai_agent, allowed_group_ids: [group.id], default_llm_id: llm_model.id) end before do @@ -26,7 +26,7 @@ describe "#ai_search_discoveries" do before do - SiteSetting.ai_bot_discover_persona = ai_persona.id + SiteSetting.ai_bot_discover_agent = ai_agent.id group.add(user) end diff --git a/spec/plugin_helper.rb b/spec/plugin_helper.rb index 0346df7ca..810f7b9ef 100644 --- a/spec/plugin_helper.rb +++ b/spec/plugin_helper.rb @@ -16,8 +16,8 @@ def assign_fake_provider_to(setting_name) end end - def assign_persona_to(setting_name, allowed_group_ids) - Fabricate(:ai_persona, allowed_group_ids: allowed_group_ids).tap do |p| + def assign_agent_to(setting_name, allowed_group_ids) + Fabricate(:ai_agent, allowed_group_ids: allowed_group_ids).tap do |p| SiteSetting.public_send("#{setting_name}=", p.id) end end diff --git a/spec/requests/admin/ai_personas_controller_spec.rb b/spec/requests/admin/ai_agents_controller_spec.rb similarity index 62% rename from spec/requests/admin/ai_personas_controller_spec.rb rename to spec/requests/admin/ai_agents_controller_spec.rb index 17b7fe34e..3f27b3908 100644 --- a/spec/requests/admin/ai_personas_controller_spec.rb +++ b/spec/requests/admin/ai_agents_controller_spec.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::Admin::AiPersonasController do +RSpec.describe DiscourseAi::Admin::AiAgentsController do fab!(:admin) - fab!(:ai_persona) + fab!(:ai_agent) fab!(:embedding_definition) fab!(:llm_model) @@ -14,17 +14,17 @@ describe "GET #index" do it "returns a success response" do - get "/admin/plugins/discourse-ai/ai-personas.json" + get "/admin/plugins/discourse-ai/ai-agents.json" expect(response).to be_successful - expect(response.parsed_body["ai_personas"].length).to eq(AiPersona.count) + expect(response.parsed_body["ai_agents"].length).to eq(AiAgent.count) expect(response.parsed_body["meta"]["tools"].length).to eq( - DiscourseAi::Personas::Persona.all_available_tools.length, + DiscourseAi::Agents::Agent.all_available_tools.length, ) end it "sideloads llms" do - get "/admin/plugins/discourse-ai/ai-personas.json" + get "/admin/plugins/discourse-ai/ai-agents.json" expect(response).to be_successful expect(response.parsed_body["meta"]["llms"]).to eq( @@ -39,38 +39,38 @@ end it "returns tool options with each tool" do - persona1 = Fabricate(:ai_persona, name: "search1", tools: ["SearchCommand"]) - persona2 = + agent1 = Fabricate(:ai_agent, name: "search1", tools: ["SearchCommand"]) + agent2 = Fabricate( - :ai_persona, + :ai_agent, name: "search2", tools: [["SearchCommand", { base_query: "test" }, true]], allow_topic_mentions: true, - allow_personal_messages: true, + allow_agentl_messages: true, allow_chat_channel_mentions: true, allow_chat_direct_messages: true, default_llm_id: llm_model.id, question_consolidator_llm_id: llm_model.id, forced_tool_count: 2, ) - persona2.create_user! + agent2.create_user! - get "/admin/plugins/discourse-ai/ai-personas.json" + get "/admin/plugins/discourse-ai/ai-agents.json" expect(response).to be_successful - serializer_persona1 = response.parsed_body["ai_personas"].find { |p| p["id"] == persona1.id } - serializer_persona2 = response.parsed_body["ai_personas"].find { |p| p["id"] == persona2.id } + serializer_agent1 = response.parsed_body["ai_agents"].find { |p| p["id"] == agent1.id } + serializer_agent2 = response.parsed_body["ai_agents"].find { |p| p["id"] == agent2.id } - expect(serializer_persona2["allow_topic_mentions"]).to eq(true) - expect(serializer_persona2["allow_personal_messages"]).to eq(true) - expect(serializer_persona2["allow_chat_channel_mentions"]).to eq(true) - expect(serializer_persona2["allow_chat_direct_messages"]).to eq(true) + expect(serializer_agent2["allow_topic_mentions"]).to eq(true) + expect(serializer_agent2["allow_agentl_messages"]).to eq(true) + expect(serializer_agent2["allow_chat_channel_mentions"]).to eq(true) + expect(serializer_agent2["allow_chat_direct_messages"]).to eq(true) - expect(serializer_persona2["default_llm_id"]).to eq(llm_model.id) - expect(serializer_persona2["question_consolidator_llm_id"]).to eq(llm_model.id) - expect(serializer_persona2["user_id"]).to eq(persona2.user_id) - expect(serializer_persona2["user"]["id"]).to eq(persona2.user_id) - expect(serializer_persona2["forced_tool_count"]).to eq(2) + expect(serializer_agent2["default_llm_id"]).to eq(llm_model.id) + expect(serializer_agent2["question_consolidator_llm_id"]).to eq(llm_model.id) + expect(serializer_agent2["user_id"]).to eq(agent2.user_id) + expect(serializer_agent2["user"]["id"]).to eq(agent2.user_id) + expect(serializer_agent2["forced_tool_count"]).to eq(2) tools = response.parsed_body["meta"]["tools"] search_tool = tools.find { |c| c["id"] == "Search" } @@ -100,8 +100,8 @@ }, ) - expect(serializer_persona1["tools"]).to eq(["SearchCommand"]) - expect(serializer_persona2["tools"]).to eq( + expect(serializer_agent1["tools"]).to eq(["SearchCommand"]) + expect(serializer_agent2["tools"]).to eq( [["SearchCommand", { "base_query" => "test" }, true]], ) end @@ -112,12 +112,12 @@ TranslationOverride.upsert!( SiteSetting.default_locale, - "discourse_ai.ai_bot.personas.general.name", + "discourse_ai.ai_bot.agents.general.name", "Général", ) TranslationOverride.upsert!( SiteSetting.default_locale, - "discourse_ai.ai_bot.personas.general.description", + "discourse_ai.ai_bot.agents.general.description", "Général Description", ) end @@ -125,44 +125,44 @@ after do TranslationOverride.revert!( SiteSetting.default_locale, - "discourse_ai.ai_bot.personas.general.name", + "discourse_ai.ai_bot.agents.general.name", ) TranslationOverride.revert!( SiteSetting.default_locale, - "discourse_ai.ai_bot.personas.general.description", + "discourse_ai.ai_bot.agents.general.description", ) end - it "returns localized persona names and descriptions" do - get "/admin/plugins/discourse-ai/ai-personas.json" + it "returns localized agent names and descriptions" do + get "/admin/plugins/discourse-ai/ai-agents.json" - id = DiscourseAi::Personas::Persona.system_personas[DiscourseAi::Personas::General] - persona = response.parsed_body["ai_personas"].find { |p| p["id"] == id } + id = DiscourseAi::Agents::Agent.system_agents[DiscourseAi::Agents::General] + agent = response.parsed_body["ai_agents"].find { |p| p["id"] == id } - expect(persona["name"]).to eq("Général") - expect(persona["description"]).to eq("Général Description") + expect(agent["name"]).to eq("Général") + expect(agent["description"]).to eq("Général Description") end end end describe "GET #edit" do it "returns a success response" do - get "/admin/plugins/discourse-ai/ai-personas/#{ai_persona.id}/edit.json" + get "/admin/plugins/discourse-ai/ai-agents/#{ai_agent.id}/edit.json" expect(response).to be_successful - expect(response.parsed_body["ai_persona"]["name"]).to eq(ai_persona.name) + expect(response.parsed_body["ai_agent"]["name"]).to eq(ai_agent.name) end - it "includes rag uploads for each persona" do + it "includes rag uploads for each agent" do upload = Fabricate(:upload) - RagDocumentFragment.link_target_and_uploads(ai_persona, [upload.id]) + RagDocumentFragment.link_target_and_uploads(ai_agent, [upload.id]) - get "/admin/plugins/discourse-ai/ai-personas/#{ai_persona.id}/edit.json" + get "/admin/plugins/discourse-ai/ai-agents/#{ai_agent.id}/edit.json" expect(response).to be_successful - serialized_persona = response.parsed_body["ai_persona"] + serialized_agent = response.parsed_body["ai_agent"] - expect(serialized_persona.dig("rag_uploads", 0, "id")).to eq(upload.id) - expect(serialized_persona.dig("rag_uploads", 0, "original_filename")).to eq( + expect(serialized_agent.dig("rag_uploads", 0, "id")).to eq(upload.id) + expect(serialized_agent.dig("rag_uploads", 0, "original_filename")).to eq( upload.original_filename, ) end @@ -179,7 +179,7 @@ top_p: 0.1, temperature: 0.5, allow_topic_mentions: true, - allow_personal_messages: true, + allow_agentl_messages: true, allow_chat_channel_mentions: true, allow_chat_direct_messages: true, default_llm_id: llm_model.id, @@ -190,44 +190,44 @@ } end - it "creates a new AiPersona" do + it "creates a new AiAgent" do expect { - post "/admin/plugins/discourse-ai/ai-personas.json", - params: { ai_persona: valid_attributes }.to_json, + post "/admin/plugins/discourse-ai/ai-agents.json", + params: { ai_agent: valid_attributes }.to_json, headers: { "CONTENT_TYPE" => "application/json", } expect(response).to be_successful - persona_json = response.parsed_body["ai_persona"] - - expect(persona_json["name"]).to eq("superbot") - expect(persona_json["top_p"]).to eq(0.1) - expect(persona_json["temperature"]).to eq(0.5) - expect(persona_json["default_llm_id"]).to eq(llm_model.id) - expect(persona_json["forced_tool_count"]).to eq(2) - expect(persona_json["allow_topic_mentions"]).to eq(true) - expect(persona_json["allow_personal_messages"]).to eq(true) - expect(persona_json["allow_chat_channel_mentions"]).to eq(true) - expect(persona_json["allow_chat_direct_messages"]).to eq(true) - expect(persona_json["question_consolidator_llm_id"]).to eq(llm_model.id) - expect(persona_json["response_format"].map { |rf| rf["key"] }).to contain_exactly( + agent_json = response.parsed_body["ai_agent"] + + expect(agent_json["name"]).to eq("superbot") + expect(agent_json["top_p"]).to eq(0.1) + expect(agent_json["temperature"]).to eq(0.5) + expect(agent_json["default_llm_id"]).to eq(llm_model.id) + expect(agent_json["forced_tool_count"]).to eq(2) + expect(agent_json["allow_topic_mentions"]).to eq(true) + expect(agent_json["allow_agentl_messages"]).to eq(true) + expect(agent_json["allow_chat_channel_mentions"]).to eq(true) + expect(agent_json["allow_chat_direct_messages"]).to eq(true) + expect(agent_json["question_consolidator_llm_id"]).to eq(llm_model.id) + expect(agent_json["response_format"].map { |rf| rf["key"] }).to contain_exactly( "summary", ) - expect(persona_json["examples"]).to eq(valid_attributes[:examples]) + expect(agent_json["examples"]).to eq(valid_attributes[:examples]) - persona = AiPersona.find(persona_json["id"]) + agent = AiAgent.find(agent_json["id"]) - expect(persona.tools).to eq([["search", { "base_query" => "test" }, true]]) - expect(persona.top_p).to eq(0.1) - expect(persona.temperature).to eq(0.5) - }.to change(AiPersona, :count).by(1) + expect(agent.tools).to eq([["search", { "base_query" => "test" }, true]]) + expect(agent.top_p).to eq(0.1) + expect(agent.temperature).to eq(0.5) + }.to change(AiAgent, :count).by(1) end end context "with invalid params" do - it "renders a JSON response with errors for the new ai_persona" do - post "/admin/plugins/discourse-ai/ai-personas.json", params: { ai_persona: { foo: "" } } # invalid attribute + it "renders a JSON response with errors for the new ai_agent" do + post "/admin/plugins/discourse-ai/ai-agents.json", params: { ai_agent: { foo: "" } } # invalid attribute expect(response).to have_http_status(:unprocessable_entity) expect(response.content_type).to include("application/json") end @@ -235,12 +235,12 @@ end describe "POST #create_user" do - it "creates a user for the persona" do - post "/admin/plugins/discourse-ai/ai-personas/#{ai_persona.id}/create-user.json" - ai_persona.reload + it "creates a user for the agent" do + post "/admin/plugins/discourse-ai/ai-agents/#{ai_agent.id}/create-user.json" + ai_agent.reload expect(response).to be_successful - expect(response.parsed_body["user"]["id"]).to eq(ai_persona.user_id) + expect(response.parsed_body["user"]["id"]).to eq(ai_agent.user_id) end end @@ -252,15 +252,15 @@ scope = ApiKeyScope.create!( resource: "discourse_ai", - action: "update_personas", + action: "update_agents", api_key_id: api_key.id, allowed_parameters: { }, ) - put "/admin/plugins/discourse-ai/ai-personas/#{ai_persona.id}.json", + put "/admin/plugins/discourse-ai/ai-agents/#{ai_agent.id}.json", params: { - ai_persona: { + ai_agent: { name: "UpdatedByAPI", description: "Updated via API key", }, @@ -271,15 +271,15 @@ } expect(response).to have_http_status(:ok) - ai_persona.reload - expect(ai_persona.name).to eq("UpdatedByAPI") - expect(ai_persona.description).to eq("Updated via API key") + ai_agent.reload + expect(ai_agent.name).to eq("UpdatedByAPI") + expect(ai_agent.description).to eq("Updated via API key") scope.update!(action: "fake") - put "/admin/plugins/discourse-ai/ai-personas/#{ai_persona.id}.json", + put "/admin/plugins/discourse-ai/ai-agents/#{ai_agent.id}.json", params: { - ai_persona: { + ai_agent: { name: "UpdatedByAPI 2", description: "Updated via API key", }, @@ -294,28 +294,28 @@ end it "allows us to trivially clear top_p and temperature" do - persona = Fabricate(:ai_persona, name: "test_bot2", top_p: 0.5, temperature: 0.1) - put "/admin/plugins/discourse-ai/ai-personas/#{persona.id}.json", + agent = Fabricate(:ai_agent, name: "test_bot2", top_p: 0.5, temperature: 0.1) + put "/admin/plugins/discourse-ai/ai-agents/#{agent.id}.json", params: { - ai_persona: { + ai_agent: { top_p: "", temperature: "", }, } expect(response).to have_http_status(:ok) - persona.reload + agent.reload - expect(persona.top_p).to eq(nil) - expect(persona.temperature).to eq(nil) + expect(agent.top_p).to eq(nil) + expect(agent.temperature).to eq(nil) end it "supports updating rag params" do - persona = Fabricate(:ai_persona, name: "test_bot2") + agent = Fabricate(:ai_agent, name: "test_bot2") - put "/admin/plugins/discourse-ai/ai-personas/#{persona.id}.json", + put "/admin/plugins/discourse-ai/ai-agents/#{agent.id}.json", params: { - ai_persona: { + ai_agent: { rag_chunk_tokens: "102", rag_chunk_overlap_tokens: "12", rag_conversation_chunks: "13", @@ -325,36 +325,36 @@ } expect(response).to have_http_status(:ok) - persona.reload + agent.reload - expect(persona.rag_chunk_tokens).to eq(102) - expect(persona.rag_chunk_overlap_tokens).to eq(12) - expect(persona.rag_conversation_chunks).to eq(13) - expect(persona.rag_llm_model_id).to eq(llm_model.id) - expect(persona.question_consolidator_llm_id).to eq(llm_model.id) + expect(agent.rag_chunk_tokens).to eq(102) + expect(agent.rag_chunk_overlap_tokens).to eq(12) + expect(agent.rag_conversation_chunks).to eq(13) + expect(agent.rag_llm_model_id).to eq(llm_model.id) + expect(agent.question_consolidator_llm_id).to eq(llm_model.id) end it "supports updating vision params" do - persona = Fabricate(:ai_persona, name: "test_bot2") - put "/admin/plugins/discourse-ai/ai-personas/#{persona.id}.json", + agent = Fabricate(:ai_agent, name: "test_bot2") + put "/admin/plugins/discourse-ai/ai-agents/#{agent.id}.json", params: { - ai_persona: { + ai_agent: { vision_enabled: true, vision_max_pixels: 512 * 512, }, } expect(response).to have_http_status(:ok) - persona.reload + agent.reload - expect(persona.vision_enabled).to eq(true) - expect(persona.vision_max_pixels).to eq(512 * 512) + expect(agent.vision_enabled).to eq(true) + expect(agent.vision_max_pixels).to eq(512 * 512) end - it "does not allow temperature and top p changes on stock personas" do - put "/admin/plugins/discourse-ai/ai-personas/#{DiscourseAi::Personas::Persona.system_personas.values.first}.json", + it "does not allow temperature and top p changes on stock agents" do + put "/admin/plugins/discourse-ai/ai-agents/#{DiscourseAi::Agents::Agent.system_agents.values.first}.json", params: { - ai_persona: { + ai_agent: { top_p: 0.5, temperature: 0.1, }, @@ -364,10 +364,10 @@ end context "with valid params" do - it "updates the requested ai_persona" do - put "/admin/plugins/discourse-ai/ai-personas/#{ai_persona.id}.json", + it "updates the requested ai_agent" do + put "/admin/plugins/discourse-ai/ai-agents/#{ai_agent.id}.json", params: { - ai_persona: { + ai_agent: { name: "SuperBot", enabled: false, tools: ["search"], @@ -377,18 +377,18 @@ expect(response).to have_http_status(:ok) expect(response.content_type).to include("application/json") - ai_persona.reload - expect(ai_persona.name).to eq("SuperBot") - expect(ai_persona.enabled).to eq(false) - expect(ai_persona.tools).to eq([["search", nil, false]]) + ai_agent.reload + expect(ai_agent.name).to eq("SuperBot") + expect(ai_agent.enabled).to eq(false) + expect(ai_agent.tools).to eq([["search", nil, false]]) end end - context "with system personas" do + context "with system agents" do it "does not allow editing of system prompts" do - put "/admin/plugins/discourse-ai/ai-personas/#{DiscourseAi::Personas::Persona.system_personas.values.first}.json", + put "/admin/plugins/discourse-ai/ai-agents/#{DiscourseAi::Agents::Agent.system_agents.values.first}.json", params: { - ai_persona: { + ai_agent: { system_prompt: "you are not a helpful bot", }, } @@ -399,9 +399,9 @@ end it "does not allow editing of tools" do - put "/admin/plugins/discourse-ai/ai-personas/#{DiscourseAi::Personas::Persona.system_personas.values.first}.json", + put "/admin/plugins/discourse-ai/ai-agents/#{DiscourseAi::Agents::Agent.system_agents.values.first}.json", params: { - ai_persona: { + ai_agent: { tools: %w[SearchCommand ImageCommand], }, } @@ -412,9 +412,9 @@ end it "does not allow editing of name and description cause it is localized" do - put "/admin/plugins/discourse-ai/ai-personas/#{DiscourseAi::Personas::Persona.system_personas.values.first}.json", + put "/admin/plugins/discourse-ai/ai-agents/#{DiscourseAi::Agents::Agent.system_agents.values.first}.json", params: { - ai_persona: { + ai_agent: { name: "bob", description: "the bob", }, @@ -426,9 +426,9 @@ end it "does allow some actions" do - put "/admin/plugins/discourse-ai/ai-personas/#{DiscourseAi::Personas::Persona.system_personas.values.first}.json", + put "/admin/plugins/discourse-ai/ai-agents/#{DiscourseAi::Agents::Agent.system_agents.values.first}.json", params: { - ai_persona: { + ai_agent: { allowed_group_ids: [Group::AUTO_GROUPS[:trust_level_1]], enabled: false, priority: 989, @@ -440,10 +440,10 @@ end context "with invalid params" do - it "renders a JSON response with errors for the ai_persona" do - put "/admin/plugins/discourse-ai/ai-personas/#{ai_persona.id}.json", + it "renders a JSON response with errors for the ai_agent" do + put "/admin/plugins/discourse-ai/ai-agents/#{ai_agent.id}.json", params: { - ai_persona: { + ai_agent: { name: "", }, } # invalid attribute @@ -454,22 +454,22 @@ end describe "DELETE #destroy" do - it "destroys the requested ai_persona" do + it "destroys the requested ai_agent" do expect { - delete "/admin/plugins/discourse-ai/ai-personas/#{ai_persona.id}.json" + delete "/admin/plugins/discourse-ai/ai-agents/#{ai_agent.id}.json" expect(response).to have_http_status(:no_content) - }.to change(AiPersona, :count).by(-1) + }.to change(AiAgent, :count).by(-1) end - it "is not allowed to delete system personas" do + it "is not allowed to delete system agents" do expect { - delete "/admin/plugins/discourse-ai/ai-personas/#{DiscourseAi::Personas::Persona.system_personas.values.first}.json" + delete "/admin/plugins/discourse-ai/ai-agents/#{DiscourseAi::Agents::Agent.system_agents.values.first}.json" expect(response).to have_http_status(:unprocessable_entity) expect(response.parsed_body["errors"].join).not_to be_blank # let's make sure this is translated expect(response.parsed_body["errors"].join).not_to include("en.discourse") - }.not_to change(AiPersona, :count) + }.not_to change(AiAgent, :count) end end @@ -481,31 +481,31 @@ after { fake_endpoint.reset! } - it "ensures persona exists" do - post "/admin/plugins/discourse-ai/ai-personas/stream-reply.json" + it "ensures agent exists" do + post "/admin/plugins/discourse-ai/ai-agents/stream-reply.json" expect(response).to have_http_status(:unprocessable_entity) # this ensures localization key is actually in the yaml - expect(response.body).to include("persona_name") + expect(response.body).to include("agent_name") end it "ensures question exists" do - ai_persona.update!(default_llm_id: llm.id) + ai_agent.update!(default_llm_id: llm.id) - post "/admin/plugins/discourse-ai/ai-personas/stream-reply.json", + post "/admin/plugins/discourse-ai/ai-agents/stream-reply.json", params: { - persona_id: ai_persona.id, + agent_id: ai_agent.id, user_unique_id: "site:test.com:user_id:1", } expect(response).to have_http_status(:unprocessable_entity) expect(response.body).to include("query") end - it "ensure persona has a user specified" do - ai_persona.update!(default_llm_id: llm.id) + it "ensure agent has a user specified" do + ai_agent.update!(default_llm_id: llm.id) - post "/admin/plugins/discourse-ai/ai-personas/stream-reply.json", + post "/admin/plugins/discourse-ai/ai-agents/stream-reply.json", params: { - persona_id: ai_persona.id, + agent_id: ai_agent.id, query: "how are you today?", user_unique_id: "site:test.com:user_id:1", } @@ -560,19 +560,19 @@ def validate_streamed_response(raw_http, expected) fake_endpoint.fake_content = ["This is a test! Testing!", "An amazing title"] - ai_persona.create_user! - ai_persona.update!( + ai_agent.create_user! + ai_agent.update!( allowed_group_ids: [Group::AUTO_GROUPS[:trust_level_0]], default_llm_id: llm.id, - allow_personal_messages: true, + allow_agentl_messages: true, system_prompt: "you are a helpful bot", ) io_out, io_in = IO.pipe - post "/admin/plugins/discourse-ai/ai-personas/stream-reply.json", + post "/admin/plugins/discourse-ai/ai-agents/stream-reply.json", params: { - persona_name: ai_persona.name, + agent_name: ai_agent.name, query: "how are you today?", user_unique_id: "site:test.com:user_id:1", preferred_username: "test_user", @@ -600,7 +600,7 @@ def validate_streamed_response(raw_http, expected) user_post = topic.posts.find_by(post_number: 1) expect(user_post.raw).to eq("how are you today?") - # need ai persona and user + # need ai agent and user expect(topic.topic_allowed_users.count).to eq(2) expect(topic.archetype).to eq(Archetype.private_message) expect(topic.title).to eq("An amazing title") @@ -613,7 +613,7 @@ def validate_streamed_response(raw_http, expected) # this simplifies function calls fake_endpoint.chunk_count = 1 - ai_persona.update!(tools: ["Categories"]) + ai_agent.update!(tools: ["Categories"]) # lets also unstage the user and add the user to tl0 # this will ensure there are no feedback loops @@ -622,14 +622,14 @@ def validate_streamed_response(raw_http, expected) Group.user_trust_level_change!(new_user.id, new_user.trust_level) # double check this happened and user is in group - personas = AiPersona.allowed_modalities(user: new_user.reload, allow_personal_messages: true) - expect(personas.count).to eq(1) + agents = AiAgent.allowed_modalities(user: new_user.reload, allow_agentl_messages: true) + expect(agents.count).to eq(1) io_out, io_in = IO.pipe - post "/admin/plugins/discourse-ai/ai-personas/stream-reply.json", + post "/admin/plugins/discourse-ai/ai-agents/stream-reply.json", params: { - persona_id: ai_persona.id, + agent_id: ai_agent.id, query: "how are you now?", user_unique_id: "site:test.com:user_id:1", preferred_username: "test_user", diff --git a/spec/requests/admin/ai_features_controller_spec.rb b/spec/requests/admin/ai_features_controller_spec.rb index 8265d856f..799e88ceb 100644 --- a/spec/requests/admin/ai_features_controller_spec.rb +++ b/spec/requests/admin/ai_features_controller_spec.rb @@ -5,8 +5,8 @@ fab!(:admin) fab!(:group) fab!(:llm_model) - fab!(:summarizer_persona) { Fabricate(:ai_persona) } - fab!(:alternate_summarizer_persona) { Fabricate(:ai_persona) } + fab!(:summarizer_agent) { Fabricate(:ai_agent) } + fab!(:alternate_summarizer_agent) { Fabricate(:ai_agent) } before do sign_in(admin) @@ -15,7 +15,7 @@ end describe "#index" do - it "lists all features backed by personas" do + it "lists all features backed by agents" do get "/admin/plugins/discourse-ai/ai-features.json" expect(response.status).to eq(200) diff --git a/spec/requests/admin/ai_llms_controller_spec.rb b/spec/requests/admin/ai_llms_controller_spec.rb index 280204f2f..4b6c53e2b 100644 --- a/spec/requests/admin/ai_llms_controller_spec.rb +++ b/spec/requests/admin/ai_llms_controller_spec.rb @@ -11,10 +11,10 @@ describe "GET #index" do fab!(:llm_model) { Fabricate(:llm_model, enabled_chat_bot: true) } fab!(:llm_model2) { Fabricate(:llm_model) } - fab!(:ai_persona) do + fab!(:ai_agent) do Fabricate( - :ai_persona, - name: "Cool persona", + :ai_agent, + name: "Cool agent", force_default_llm: true, default_llm_id: llm_model2.id, ) @@ -79,7 +79,7 @@ model2_json = llms.find { |m| m["id"] == llm_model2.id } expect(model2_json["used_by"]).to contain_exactly( - { "type" => "ai_persona", "name" => "Cool persona", "id" => ai_persona.id }, + { "type" => "ai_agent", "name" => "Cool agent", "id" => ai_agent.id }, { "type" => "ai_summarization" }, { "type" => "ai_embeddings_semantic_search" }, ) @@ -450,7 +450,7 @@ describe "DELETE #destroy" do fab!(:llm_model) - it "destroys the requested ai_persona" do + it "destroys the requested ai_agent" do expect { delete "/admin/plugins/discourse-ai/ai-llms/#{llm_model.id}.json" diff --git a/spec/requests/admin/rag_document_fragments_controller_spec.rb b/spec/requests/admin/rag_document_fragments_controller_spec.rb index 24b4b387c..aee074b27 100644 --- a/spec/requests/admin/rag_document_fragments_controller_spec.rb +++ b/spec/requests/admin/rag_document_fragments_controller_spec.rb @@ -2,7 +2,7 @@ RSpec.describe DiscourseAi::Admin::RagDocumentFragmentsController do fab!(:admin) - fab!(:ai_persona) + fab!(:ai_agent) fab!(:vector_def) { Fabricate(:embedding_definition) } @@ -15,8 +15,8 @@ after { @cleanup_files&.each(&:unlink) } describe "GET #indexing_status_check" do - it "works for AiPersona" do - get "/admin/plugins/discourse-ai/rag-document-fragments/files/status.json?target_type=AiPersona&target_id=#{ai_persona.id}" + it "works for AiAgent" do + get "/admin/plugins/discourse-ai/rag-document-fragments/files/status.json?target_type=AiAgent&target_id=#{ai_agent.id}" expect(response.parsed_body).to eq({}) expect(response.status).to eq(200) diff --git a/spec/requests/ai_bot/bot_controller_spec.rb b/spec/requests/ai_bot/bot_controller_spec.rb index 385c37e0c..d2a6d108a 100644 --- a/spec/requests/ai_bot/bot_controller_spec.rb +++ b/spec/requests/ai_bot/bot_controller_spec.rb @@ -127,9 +127,9 @@ before { SiteSetting.ai_bot_enabled = true } fab!(:group) - fab!(:ai_persona) { Fabricate(:ai_persona, allowed_group_ids: [group.id], default_llm_id: 1) } + fab!(:ai_agent) { Fabricate(:ai_agent, allowed_group_ids: [group.id], default_llm_id: 1) } - context "when no persona is selected" do + context "when no agent is selected" do it "returns a 403" do get "/discourse-ai/ai-bot/discover", params: { query: "What is Discourse?" } @@ -137,8 +137,8 @@ end end - context "when the user doesn't have access to the persona" do - before { SiteSetting.ai_bot_discover_persona = ai_persona.id } + context "when the user doesn't have access to the agent" do + before { SiteSetting.ai_bot_discover_agent = ai_agent.id } it "returns a 403" do get "/discourse-ai/ai-bot/discover", params: { query: "What is Discourse?" } @@ -149,7 +149,7 @@ context "when the user is allowed to use discover" do before do - SiteSetting.ai_bot_discover_persona = ai_persona.id + SiteSetting.ai_bot_discover_agent = ai_agent.id group.add(user) end @@ -173,17 +173,17 @@ before { SiteSetting.ai_bot_enabled = true } fab!(:group) fab!(:llm_model) - fab!(:ai_persona) do - persona = Fabricate(:ai_persona, allowed_group_ids: [group.id], default_llm_id: llm_model.id) - persona.create_user! - persona + fab!(:ai_agent) do + agent = Fabricate(:ai_agent, allowed_group_ids: [group.id], default_llm_id: llm_model.id) + agent.create_user! + agent end let(:query) { "What is Discourse?" } let(:context) { "Discourse is an open-source discussion platform." } context "when the user is allowed to discover" do before do - SiteSetting.ai_bot_discover_persona = ai_persona.id + SiteSetting.ai_bot_discover_agent = ai_agent.id group.add(user) end diff --git a/spec/serializers/ai_features_agent_serializer_spec.rb b/spec/serializers/ai_features_agent_serializer_spec.rb new file mode 100644 index 000000000..5663fe9a1 --- /dev/null +++ b/spec/serializers/ai_features_agent_serializer_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +RSpec.describe AiFeaturesAgentSerializer do + fab!(:admin) + fab!(:ai_agent) + fab!(:group) + fab!(:group_2) { Fabricate(:group) } + + describe "serialized attributes" do + before do + ai_agent.allowed_group_ids = [group.id, group_2.id] + ai_agent.save! + end + + context "when there is a agent with allowed groups" do + let(:allowed_groups) do + Group + .where(id: ai_agent.allowed_group_ids) + .pluck(:id, :name) + .map { |id, name| { id: id, name: name } } + end + + it "display every participant" do + serialized = described_class.new(ai_agent, scope: Guardian.new(admin), root: nil) + expect(serialized.id).to eq(ai_agent.id) + expect(serialized.name).to eq(ai_agent.name) + expect(serialized.system_prompt).to eq(ai_agent.system_prompt) + expect(serialized.allowed_groups).to eq(allowed_groups) + expect(serialized.enabled).to eq(ai_agent.enabled) + end + end + end +end diff --git a/spec/serializers/ai_features_persona_serializer_spec.rb b/spec/serializers/ai_features_persona_serializer_spec.rb deleted file mode 100644 index 677e2743c..000000000 --- a/spec/serializers/ai_features_persona_serializer_spec.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe AiFeaturesPersonaSerializer do - fab!(:admin) - fab!(:ai_persona) - fab!(:group) - fab!(:group_2) { Fabricate(:group) } - - describe "serialized attributes" do - before do - ai_persona.allowed_group_ids = [group.id, group_2.id] - ai_persona.save! - end - - context "when there is a persona with allowed groups" do - let(:allowed_groups) do - Group - .where(id: ai_persona.allowed_group_ids) - .pluck(:id, :name) - .map { |id, name| { id: id, name: name } } - end - - it "display every participant" do - serialized = described_class.new(ai_persona, scope: Guardian.new(admin), root: nil) - expect(serialized.id).to eq(ai_persona.id) - expect(serialized.name).to eq(ai_persona.name) - expect(serialized.system_prompt).to eq(ai_persona.system_prompt) - expect(serialized.allowed_groups).to eq(allowed_groups) - expect(serialized.enabled).to eq(ai_persona.enabled) - end - end - end -end diff --git a/spec/system/admin_ai_agent_spec.rb b/spec/system/admin_ai_agent_spec.rb new file mode 100644 index 000000000..d3d2b51e7 --- /dev/null +++ b/spec/system/admin_ai_agent_spec.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +RSpec.describe "Admin AI agent configuration", type: :system, js: true do + fab!(:admin) + let(:page_header) { PageObjects::Components::DPageHeader.new } + let(:form) { PageObjects::Components::FormKit.new("form") } + + before do + SiteSetting.ai_bot_enabled = true + sign_in(admin) + end + + it "allows creation of a agent" do + visit "/admin/plugins/discourse-ai/ai-agents" + + expect(page_header).to be_visible + + find(".ai-agent-list-editor__new-button").click() + + expect(page_header).to be_hidden + + form.field("name").fill_in("Test Agent") + form.field("description").fill_in("I am a test agent") + form.field("system_prompt").fill_in("You are a helpful bot") + + tool_selector = PageObjects::Components::SelectKit.new("#control-tools .select-kit") + tool_selector.expand + tool_selector.select_row_by_value("Read") + tool_selector.select_row_by_value("ListCategories") + tool_selector.collapse + + tool_selector = PageObjects::Components::SelectKit.new("#control-forcedTools .select-kit") + tool_selector.expand + tool_selector.select_row_by_value("ListCategories") + tool_selector.select_row_by_value("Read") + tool_selector.collapse + + form.field("forced_tool_count").select(1) + + form.submit + + expect(page).not_to have_current_path("/admin/plugins/discourse-ai/ai-agents/new") + + agent_id = page.current_path.split("/")[-2].to_i + + agent = AiAgent.find(agent_id) + expect(agent.name).to eq("Test Agent") + expect(agent.description).to eq("I am a test agent") + expect(agent.system_prompt).to eq("You are a helpful bot") + expect(agent.forced_tool_count).to eq(1) + + expected_tools = [["Read", { "read_private" => nil }, true], ["ListCategories", {}, true]] + expect(agent.tools).to contain_exactly(*expected_tools) + end + + it "will not allow deletion or editing of system agents" do + visit "/admin/plugins/discourse-ai/ai-agents/#{DiscourseAi::Agents::Agent.system_agents.values.first}/edit" + expect(page).not_to have_selector(".ai-agent-editor__delete") + expect(form.field("system_prompt")).to be_disabled + end + + it "will enable agent right away when you click on enable but does not save side effects" do + agent = Fabricate(:ai_agent, enabled: false) + + visit "/admin/plugins/discourse-ai/ai-agents/#{agent.id}/edit" + + form.field("name").fill_in("Test Agent 1") + form.field("enabled").toggle + + try_until_success { expect(agent.reload.enabled).to eq(true) } + + agent.reload + expect(agent.enabled).to eq(true) + expect(agent.name).not_to eq("Test Agent 1") + end + + it "enabling a agent doesn't reset other fields" do + agent = Fabricate(:ai_agent, enabled: false) + updated_name = "Update agent 1" + + visit "/admin/plugins/discourse-ai/ai-agents/#{agent.id}/edit" + + form.field("name").fill_in(updated_name) + form.field("enabled").toggle + + try_until_success { expect(agent.reload.enabled).to eq(true) } + + expect(form.field("name").value).to eq(updated_name) + end + + it "toggling a agent's priority doesn't reset other fields" do + agent = Fabricate(:ai_agent, priority: false) + updated_name = "Update agent 1" + + visit "/admin/plugins/discourse-ai/ai-agents/#{agent.id}/edit" + + form.field("name").fill_in(updated_name) + form.field("priority").toggle + + try_until_success { expect(agent.reload.priority).to eq(true) } + + expect(form.field("name").value).to eq(updated_name) + end + + it "can navigate the AI plugin with breadcrumbs" do + visit "/admin/plugins/discourse-ai/ai-agents" + expect(page).to have_css(".d-breadcrumbs") + expect(page).to have_css(".d-breadcrumbs__item", count: 4) + find(".d-breadcrumbs__item", text: I18n.t("admin_js.admin.plugins.title")).click + expect(page).to have_current_path("/admin/plugins") + end +end diff --git a/spec/system/admin_ai_features_spec.rb b/spec/system/admin_ai_features_spec.rb index 613cd78cd..51c068a02 100644 --- a/spec/system/admin_ai_features_spec.rb +++ b/spec/system/admin_ai_features_spec.rb @@ -3,7 +3,7 @@ RSpec.describe "Admin AI features configuration", type: :system, js: true do fab!(:admin) fab!(:llm_model) - fab!(:summarization_persona) { Fabricate(:ai_persona) } + fab!(:summarization_agent) { Fabricate(:ai_agent) } fab!(:group_1) { Fabricate(:group) } fab!(:group_2) { Fabricate(:group) } let(:page_header) { PageObjects::Components::DPageHeader.new } @@ -11,15 +11,15 @@ let(:ai_features_page) { PageObjects::Pages::AdminAiFeatures.new } before do - summarization_persona.allowed_group_ids = [group_1.id, group_2.id] - summarization_persona.save! + summarization_agent.allowed_group_ids = [group_1.id, group_2.id] + summarization_agent.save! assign_fake_provider_to(:ai_summarization_model) SiteSetting.ai_summarization_enabled = true - SiteSetting.ai_summarization_persona = summarization_persona.id + SiteSetting.ai_summarization_agent = summarization_agent.id sign_in(admin) end - it "lists all persona backed AI features separated by configured/unconfigured" do + it "lists all agent backed AI features separated by configured/unconfigured" do ai_features_page.visit expect( ai_features_page @@ -32,9 +32,9 @@ expect(ai_features_page).to have_unconfigured_feature_items(3) end - it "lists the persona used for the corresponding AI feature" do + it "lists the agent used for the corresponding AI feature" do ai_features_page.visit - expect(ai_features_page).to have_feature_persona(summarization_persona.name) + expect(ai_features_page).to have_feature_agent(summarization_agent.name) end it "lists the groups allowed to use the AI feature" do diff --git a/spec/system/admin_ai_persona_spec.rb b/spec/system/admin_ai_persona_spec.rb deleted file mode 100644 index add89b4d6..000000000 --- a/spec/system/admin_ai_persona_spec.rb +++ /dev/null @@ -1,112 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe "Admin AI persona configuration", type: :system, js: true do - fab!(:admin) - let(:page_header) { PageObjects::Components::DPageHeader.new } - let(:form) { PageObjects::Components::FormKit.new("form") } - - before do - SiteSetting.ai_bot_enabled = true - sign_in(admin) - end - - it "allows creation of a persona" do - visit "/admin/plugins/discourse-ai/ai-personas" - - expect(page_header).to be_visible - - find(".ai-persona-list-editor__new-button").click() - - expect(page_header).to be_hidden - - form.field("name").fill_in("Test Persona") - form.field("description").fill_in("I am a test persona") - form.field("system_prompt").fill_in("You are a helpful bot") - - tool_selector = PageObjects::Components::SelectKit.new("#control-tools .select-kit") - tool_selector.expand - tool_selector.select_row_by_value("Read") - tool_selector.select_row_by_value("ListCategories") - tool_selector.collapse - - tool_selector = PageObjects::Components::SelectKit.new("#control-forcedTools .select-kit") - tool_selector.expand - tool_selector.select_row_by_value("ListCategories") - tool_selector.select_row_by_value("Read") - tool_selector.collapse - - form.field("forced_tool_count").select(1) - - form.submit - - expect(page).not_to have_current_path("/admin/plugins/discourse-ai/ai-personas/new") - - persona_id = page.current_path.split("/")[-2].to_i - - persona = AiPersona.find(persona_id) - expect(persona.name).to eq("Test Persona") - expect(persona.description).to eq("I am a test persona") - expect(persona.system_prompt).to eq("You are a helpful bot") - expect(persona.forced_tool_count).to eq(1) - - expected_tools = [["Read", { "read_private" => nil }, true], ["ListCategories", {}, true]] - expect(persona.tools).to contain_exactly(*expected_tools) - end - - it "will not allow deletion or editing of system personas" do - visit "/admin/plugins/discourse-ai/ai-personas/#{DiscourseAi::Personas::Persona.system_personas.values.first}/edit" - expect(page).not_to have_selector(".ai-persona-editor__delete") - expect(form.field("system_prompt")).to be_disabled - end - - it "will enable persona right away when you click on enable but does not save side effects" do - persona = Fabricate(:ai_persona, enabled: false) - - visit "/admin/plugins/discourse-ai/ai-personas/#{persona.id}/edit" - - form.field("name").fill_in("Test Persona 1") - form.field("enabled").toggle - - try_until_success { expect(persona.reload.enabled).to eq(true) } - - persona.reload - expect(persona.enabled).to eq(true) - expect(persona.name).not_to eq("Test Persona 1") - end - - it "enabling a persona doesn't reset other fields" do - persona = Fabricate(:ai_persona, enabled: false) - updated_name = "Update persona 1" - - visit "/admin/plugins/discourse-ai/ai-personas/#{persona.id}/edit" - - form.field("name").fill_in(updated_name) - form.field("enabled").toggle - - try_until_success { expect(persona.reload.enabled).to eq(true) } - - expect(form.field("name").value).to eq(updated_name) - end - - it "toggling a persona's priority doesn't reset other fields" do - persona = Fabricate(:ai_persona, priority: false) - updated_name = "Update persona 1" - - visit "/admin/plugins/discourse-ai/ai-personas/#{persona.id}/edit" - - form.field("name").fill_in(updated_name) - form.field("priority").toggle - - try_until_success { expect(persona.reload.priority).to eq(true) } - - expect(form.field("name").value).to eq(updated_name) - end - - it "can navigate the AI plugin with breadcrumbs" do - visit "/admin/plugins/discourse-ai/ai-personas" - expect(page).to have_css(".d-breadcrumbs") - expect(page).to have_css(".d-breadcrumbs__item", count: 4) - find(".d-breadcrumbs__item", text: I18n.t("admin_js.admin.plugins.title")).click - expect(page).to have_current_path("/admin/plugins") - end -end diff --git a/spec/system/ai_bot/agent_spec.rb b/spec/system/ai_bot/agent_spec.rb new file mode 100644 index 000000000..31e38c4d7 --- /dev/null +++ b/spec/system/ai_bot/agent_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +RSpec.describe "AI agents", type: :system, js: true do + fab!(:admin) + fab!(:gpt_4) { Fabricate(:llm_model, name: "gpt-4") } + + before do + SiteSetting.ai_bot_enabled = true + toggle_enabled_bots(bots: [gpt_4]) + sign_in(admin) + end + + it "remembers the last selected agent" do + visit "/" + find(".d-header .ai-bot-button").click() + agent_selector = + PageObjects::Components::SelectKit.new(".agent-llm-selector__agent-dropdown") + + id = DiscourseAi::Agents::Agent.all(user: admin).first.id + + expect(agent_selector).to have_selected_value(id) + + agent_selector.expand + agent_selector.select_row_by_value(-2) + + visit "/" + find(".d-header .ai-bot-button").click() + agent_selector = + PageObjects::Components::SelectKit.new(".agent-llm-selector__agent-dropdown") + agent_selector.expand + expect(agent_selector).to have_selected_value(-2) + end +end diff --git a/spec/system/ai_bot/ai_bot_helper_spec.rb b/spec/system/ai_bot/ai_bot_helper_spec.rb index b777c45ad..1f44adf6c 100644 --- a/spec/system/ai_bot/ai_bot_helper_spec.rb +++ b/spec/system/ai_bot/ai_bot_helper_spec.rb @@ -24,24 +24,24 @@ group.add(user) group.save - allowed_persona = AiPersona.last - allowed_persona.update!(allowed_group_ids: [group.id], enabled: true) + allowed_agent = AiAgent.last + allowed_agent.update!(allowed_group_ids: [group.id], enabled: true) visit "/latest" expect(page).to have_selector(".ai-bot-button") find(".ai-bot-button").click - find(".gpt-persona").click - expect(page).to have_css(".gpt-persona ul li", count: 1) + find(".gpt-agent").click + expect(page).to have_css(".gpt-agent ul li", count: 1) find(".llm-selector").click expect(page).to have_css(".llm-selector ul li", count: 2) expect(page).to have_selector(".d-editor-container") - # lets disable bots but still allow 1 persona - allowed_persona.create_user! - allowed_persona.update!(default_llm_id: gpt_4.id) + # lets disable bots but still allow 1 agent + allowed_agent.create_user! + allowed_agent.update!(default_llm_id: gpt_4.id) gpt_4.update!(enabled_chat_bot: false) gpt_3_5_turbo.update!(enabled_chat_bot: false) @@ -49,8 +49,8 @@ visit "/latest" find(".ai-bot-button").click - find(".gpt-persona").click - expect(page).to have_css(".gpt-persona ul li", count: 1) + find(".gpt-agent").click + expect(page).to have_css(".gpt-agent ul li", count: 1) expect(page).not_to have_selector(".llm-selector") SiteSetting.ai_bot_add_to_header = false diff --git a/spec/system/ai_bot/homepage_spec.rb b/spec/system/ai_bot/homepage_spec.rb index 3510c32fd..8428eddeb 100644 --- a/spec/system/ai_bot/homepage_spec.rb +++ b/spec/system/ai_bot/homepage_spec.rb @@ -36,12 +36,12 @@ claude_2.reload.user end fab!(:bot) do - persona = - AiPersona - .find(DiscourseAi::Personas::Persona.system_personas[DiscourseAi::Personas::General]) + agent = + AiAgent + .find(DiscourseAi::Agents::Agent.system_agents[DiscourseAi::Agents::General]) .class_instance .new - DiscourseAi::Personas::Bot.as(bot_user, persona: persona) + DiscourseAi::Agents::Bot.as(bot_user, agent: agent) end fab!(:pm) do @@ -74,23 +74,23 @@ fab!(:topic_user) { Fabricate(:topic_user, topic: pm, user: user) } fab!(:topic_bot_user) { Fabricate(:topic_user, topic: pm, user: bot_user) } - fab!(:persona) do - persona = - AiPersona.create!( - name: "Test Persona", - description: "A test persona", + fab!(:agent) do + agent = + AiAgent.create!( + name: "Test Agent", + description: "A test agent", allowed_group_ids: [Group::AUTO_GROUPS[:trust_level_0]], enabled: true, system_prompt: "You are a helpful bot", ) - persona.create_user! - persona.update!( + agent.create_user! + agent.update!( default_llm_id: claude_2.id, allow_chat_channel_mentions: true, allow_topic_mentions: true, ) - persona + agent end before do @@ -334,7 +334,7 @@ expect(ai_pm_homepage).to have_empty_state end - it "Allows choosing persona and LLM" do + it "Allows choosing agent and LLM" do ai_pm_homepage.visit ai_pm_homepage.llm_selector.expand diff --git a/spec/system/ai_bot/persona_spec.rb b/spec/system/ai_bot/persona_spec.rb deleted file mode 100644 index 1cf5e231a..000000000 --- a/spec/system/ai_bot/persona_spec.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe "AI personas", type: :system, js: true do - fab!(:admin) - fab!(:gpt_4) { Fabricate(:llm_model, name: "gpt-4") } - - before do - SiteSetting.ai_bot_enabled = true - toggle_enabled_bots(bots: [gpt_4]) - sign_in(admin) - end - - it "remembers the last selected persona" do - visit "/" - find(".d-header .ai-bot-button").click() - persona_selector = - PageObjects::Components::SelectKit.new(".persona-llm-selector__persona-dropdown") - - id = DiscourseAi::Personas::Persona.all(user: admin).first.id - - expect(persona_selector).to have_selected_value(id) - - persona_selector.expand - persona_selector.select_row_by_value(-2) - - visit "/" - find(".d-header .ai-bot-button").click() - persona_selector = - PageObjects::Components::SelectKit.new(".persona-llm-selector__persona-dropdown") - persona_selector.expand - expect(persona_selector).to have_selected_value(-2) - end -end diff --git a/spec/system/ai_bot/share_spec.rb b/spec/system/ai_bot/share_spec.rb index 9e8c68034..43a99a264 100644 --- a/spec/system/ai_bot/share_spec.rb +++ b/spec/system/ai_bot/share_spec.rb @@ -43,14 +43,14 @@ page.execute_script("window.navigator.clipboard.writeText('')") end - it "can share a conversation with a persona user" do + it "can share a conversation with a agent user" do clip_text = nil - persona = Fabricate(:ai_persona, name: "Tester") - persona.create_user! + agent = Fabricate(:ai_agent, name: "Tester") + agent.create_user! Fabricate(:post, topic: pm, user: admin, raw: "How do I do stuff?") - Fabricate(:post, topic: pm, user: persona.user, raw: "No idea") + Fabricate(:post, topic: pm, user: agent.user, raw: "No idea") visit(pm.url) diff --git a/spec/system/ai_bot/tool_spec.rb b/spec/system/ai_bot/tool_spec.rb index b1bc90fd3..6cd30f751 100644 --- a/spec/system/ai_bot/tool_spec.rb +++ b/spec/system/ai_bot/tool_spec.rb @@ -65,7 +65,7 @@ def ensure_can_run_test expect(page.first(required_toggle_css).checked?).to eq(true) expect(page.first(enum_toggle_css).checked?).to eq(false) - visit "/admin/plugins/discourse-ai/ai-personas/new" + visit "/admin/plugins/discourse-ai/ai-agents/new" tool_id = AiTool.order("id desc").limit(1).pluck(:id).first tool_selector = PageObjects::Components::SelectKit.new("#control-tools .select-kit") diff --git a/spec/system/ai_helper/ai_split_topic_suggestion_spec.rb b/spec/system/ai_helper/ai_split_topic_suggestion_spec.rb index 12a1109df..b12de8671 100644 --- a/spec/system/ai_helper/ai_split_topic_suggestion_spec.rb +++ b/spec/system/ai_helper/ai_split_topic_suggestion_spec.rb @@ -18,7 +18,7 @@ Fabricate( :post, topic: topic, - raw: "I prefer to eat croissants. They are my personal favorite dessert!", + raw: "I prefer to eat croissants. They are my agentl favorite dessert!", ) end fab!(:post_3) do diff --git a/spec/system/page_objects/components/ai_pm_homepage.rb b/spec/system/page_objects/components/ai_pm_homepage.rb index 69b93af37..ceee2b647 100644 --- a/spec/system/page_objects/components/ai_pm_homepage.rb +++ b/spec/system/page_objects/components/ai_pm_homepage.rb @@ -23,7 +23,7 @@ def has_too_short_dialog? text: I18n.t( "js.discourse_ai.ai_bot.conversations.min_input_length_message", - count: SiteSetting.min_personal_message_post_length, + count: SiteSetting.min_agentl_message_post_length, ), ) end @@ -62,12 +62,12 @@ def click_fist_sidebar_conversation ).click end - def persona_selector - PageObjects::Components::SelectKit.new(".persona-llm-selector__persona-dropdown") + def agent_selector + PageObjects::Components::SelectKit.new(".agent-llm-selector__agent-dropdown") end def llm_selector - PageObjects::Components::SelectKit.new(".persona-llm-selector__llm-dropdown") + PageObjects::Components::SelectKit.new(".agent-llm-selector__llm-dropdown") end def has_sidebar_back_link? diff --git a/spec/system/page_objects/pages/admin_ai_features.rb b/spec/system/page_objects/pages/admin_ai_features.rb index e6ad9efd9..dcf374cb3 100644 --- a/spec/system/page_objects/pages/admin_ai_features.rb +++ b/spec/system/page_objects/pages/admin_ai_features.rb @@ -27,9 +27,9 @@ def has_unconfigured_feature_items?(count) page.has_css?("#{UNCONFIGURED_FEATURES_TABLE} .ai-feature-list__row", count: count) end - def has_feature_persona?(name) + def has_feature_agent?(name) page.has_css?( - "#{CONFIGURED_FEATURES_TABLE} .ai-feature-list__persona .d-button-label ", + "#{CONFIGURED_FEATURES_TABLE} .ai-feature-list__agent .d-button-label ", text: name, ) end diff --git a/spec/system/summarization/chat_summarization_spec.rb b/spec/system/summarization/chat_summarization_spec.rb index dbb78417d..3f5b8050f 100644 --- a/spec/system/summarization/chat_summarization_spec.rb +++ b/spec/system/summarization/chat_summarization_spec.rb @@ -12,7 +12,7 @@ group.add(current_user) assign_fake_provider_to(:ai_summarization_model) - assign_persona_to(:ai_summarization_persona, [group.id]) + assign_agent_to(:ai_summarization_agent, [group.id]) SiteSetting.ai_summarization_enabled = true SiteSetting.chat_enabled = true diff --git a/spec/system/summarization/topic_summarization_spec.rb b/spec/system/summarization/topic_summarization_spec.rb index 30b147a02..a9fccd327 100644 --- a/spec/system/summarization/topic_summarization_spec.rb +++ b/spec/system/summarization/topic_summarization_spec.rb @@ -24,7 +24,7 @@ group.add(current_user) assign_fake_provider_to(:ai_summarization_model) - assign_persona_to(:ai_summarization_persona, [group.id]) + assign_agent_to(:ai_summarization_agent, [group.id]) SiteSetting.ai_summarization_enabled = true sign_in(current_user) diff --git a/test/javascripts/unit/models/ai-persona-test.js b/test/javascripts/unit/models/ai-agent-test.js similarity index 64% rename from test/javascripts/unit/models/ai-persona-test.js rename to test/javascripts/unit/models/ai-agent-test.js index a21d51a22..c4c834841 100644 --- a/test/javascripts/unit/models/ai-persona-test.js +++ b/test/javascripts/unit/models/ai-agent-test.js @@ -1,7 +1,7 @@ import { module, test } from "qunit"; -import AiPersona from "discourse/plugins/discourse-ai/discourse/admin/models/ai-persona"; +import AiAgent from "discourse/plugins/discourse-ai/discourse/admin/models/ai-agent"; -module("Discourse AI | Unit | Model | ai-persona", function () { +module("Discourse AI | Unit | Model | ai-agent", function () { test("toPOJO", function (assert) { const properties = { tools: [ @@ -11,15 +11,15 @@ module("Discourse AI | Unit | Model | ai-persona", function () { ], }; - const aiPersonaPOJO = AiPersona.create(properties).toPOJO(); + const aiAgentPOJO = AiAgent.create(properties).toPOJO(); - assert.deepEqual(aiPersonaPOJO.tools, [ + assert.deepEqual(aiAgentPOJO.tools, [ "ToolName", "ToolName2", "ToolName3", ]); - assert.equal(aiPersonaPOJO.toolOptions["ToolName"].option1, "value1"); - assert.equal(aiPersonaPOJO.toolOptions["ToolName"].option2, "value2"); + assert.equal(aiAgentPOJO.toolOptions["ToolName"].option1, "value1"); + assert.equal(aiAgentPOJO.toolOptions["ToolName"].option2, "value2"); }); test("fromPOJO", function (assert) { @@ -51,23 +51,23 @@ module("Discourse AI | Unit | Model | ai-persona", function () { allow_chat: false, tool_details: true, forced_tool_count: -1, - allow_personal_messages: true, + allow_agentl_messages: true, allow_topic_mentions: true, allow_chat_channel_mentions: true, allow_chat_direct_messages: true, }; const updatedValue = "updated"; - const aiPersona = AiPersona.create({ ...properties }); + const aiAgent = AiAgent.create({ ...properties }); - const personaPOJO = aiPersona.toPOJO(); + const agentPOJO = aiAgent.toPOJO(); - personaPOJO.toolOptions["ToolName"].option1 = updatedValue; - personaPOJO.forcedTools = "ToolName"; + agentPOJO.toolOptions["ToolName"].option1 = updatedValue; + agentPOJO.forcedTools = "ToolName"; - const updatedPersona = aiPersona.fromPOJO(personaPOJO); + const updatedAgent = aiAgent.fromPOJO(agentPOJO); - assert.deepEqual(updatedPersona.tools, [ + assert.deepEqual(updatedAgent.tools, [ ["ToolName", { option1: updatedValue }, true], ]); });