+
-
+
@@ -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 3ee03114..7e44b008 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 00000000..e0ba1c1b
--- /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 aa50ae60..6f40dd53 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 9e86d3f0..a17fed96 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 38cf1b50..68276365 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 f5d9d95e..e5feef9a 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 5413a3a3..1f168eb7 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 69cadec1..00000000
--- 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 e3e2dcbd..99f0c546 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 3f987698..96028481 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 af637159..44a596c6 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 e540f744..0117d1a2 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 3eaaaf71..d3190aa7 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 6662a628..1506de9d 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 6256656f..05631abf 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 adab3654..e1bf6a01 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 913f0bf9..91fce0f2 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 1749ba98..0dd99ad7 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 391f5cbc..c94c0966 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 822e6ffd..92ccf54d 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 339cb944..d5f9434b 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 6a29541c..05ccd103 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 c6f0a02d..c94320a5 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 a5fdfe5b..ac93b558 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 07d0cc2c..04f03ec3 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 6353f526..94e322d1 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 ac7d8669..b957c511 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 88214c5f..24edecde 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 4a71b0db..edfc1a2a 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 a41c966a..3a034d6d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -63,12 +63,12 @@ Discourse::Application.routes.draw do :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 @@ Discourse::Application.routes.draw do 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 00000000..215ea6ef --- /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 00000000..aea1de07 --- /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 cbe6c1ae..584c2889 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 @@ if defined?(DiscourseAutomation) 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 @@ if defined?(DiscourseAutomation) 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 0bfc46e6..144496e6 100644 --- a/discourse_automation/llm_triage.rb +++ b/discourse_automation/llm_triage.rb @@ -10,7 +10,7 @@ if defined?(DiscourseAutomation) 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 @@ if defined?(DiscourseAutomation) 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 @@ if defined?(DiscourseAutomation) 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 @@ if defined?(DiscourseAutomation) 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 624830f1..404d0ec3 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 af96c0fb..d8ba29df 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 3942cdf8..85fc4c86 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 93e2361e..8741e9fe 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 b6e852c5..91e9231d 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 @@ module DiscourseAi # 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 @@ module DiscourseAi 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 @@ module DiscourseAi 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 @@ module DiscourseAi 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 @@ module DiscourseAi 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 @@ module DiscourseAi **llm_kwargs, ) do |partial| tool = - persona.find_tool( + agent.find_tool( partial, bot_user: user, llm: current_llm, @@ -183,7 +183,7 @@ module DiscourseAi end def returns_json? - persona.response_format.present? + agent.response_format.present? end private @@ -285,7 +285,7 @@ module DiscourseAi 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 69d86669..18bd4a5f 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 407c2240..30a54015 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 851756c8..85a907d0 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 f2aa8dea..042de678 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 2e9db142..246b1c3c 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 a94d9e46..fde18043 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 01c05e08..6d7840c3 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 bd75624d..e2f53455 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 62426f77..8d4433f2 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 @@ module DiscourseAi false end - def system_personas - @system_personas ||= { + def system_agents + @system_agents ||= { General => -1, SqlHelper => -2, Artist => -3, @@ -55,17 +55,17 @@ module DiscourseAi } 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 @@ module DiscourseAi 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 @@ module DiscourseAi 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 @@ module DiscourseAi 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 @@ module DiscourseAi 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 @@ module DiscourseAi 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 @@ module DiscourseAi 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 @@ module DiscourseAi 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 f1e0c476..a2ee95fd 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 6650836b..115e4ae4 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 77ae45e3..80d25ac5 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 26af56b9..56a4d415 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 720bc145..bb8eef1f 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 @@ module DiscourseAi ``` 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 fe5d496b..6d42bd5b 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 6ce68476..2e1cd32e 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 @@ module DiscourseAi 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 @@ module DiscourseAi 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 @@ module DiscourseAi 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 @@ module DiscourseAi ) 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 @@ module DiscourseAi ) 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 @@ module DiscourseAi "allow_chat_channel_mentions", "allow_chat_direct_messages", "allow_topic_mentions", - "allow_personal_messages", + "allow_agentl_messages", ), } end @@ -511,19 +511,19 @@ module DiscourseAi ) 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 @@ module DiscourseAi 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 @@ module DiscourseAi } ) else - return { error: persona.errors.full_messages.join(", ") } + return { error: agent.errors.full_messages.join(", ") } end end end, @@ -612,7 +612,7 @@ module DiscourseAi 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 @@ module DiscourseAi 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 548fb9aa..54d44420 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 8e2971fa..8d76dd37 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 29dbb12d..b1d322f5 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 @@ module DiscourseAi 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 @@ module DiscourseAi @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 1daa7ee1..030ceec9 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 b0a6f296..30ee4919 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 5fdfb76e..80b7c6b8 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 b9e3249a..97d7adcc 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 aef00c1e..0be8fd3d 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 afbe51f9..699c1ee7 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 3bb93d02..be158f4e 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 c97cbd7c..bcff9cb5 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 bf90fcdb..642238f4 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 3ab2b705..8f888339 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 23d7c1b1..33b1e532 100644 --- a/lib/personas/tools/javascript_evaluator.rb +++ b/lib/agents/tools/javascript_evaluator.rb @@ -4,7 +4,7 @@ require "mini_racer" 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 895eaae5..6cd2ab96 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 852c9abc..83313ec8 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 777475b8..4c61fe2a 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 1abd537f..1a9da699 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 3077de47..24bcde52 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 e3074d71..fa4dfbb4 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 d8221a5e..38a4c510 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 869cff58..0f4a60dc 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 fc66c926..68e84fbb 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 7fb7c6b7..2d82850b 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 94f6cf49..af237ab1 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 da3d4f43..50d5dcdd 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 540204c2..87b71c1e 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 @@ module DiscourseAi 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 @@ module DiscourseAi 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 46038d7a..f054d81c 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 @@ module DiscourseAi } 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 15d5ed1e..7b79a654 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 8fe5ef1c..8a870812 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 444baaa9..b40208ca 100644 --- a/lib/ai_bot/entry_point.rb +++ b/lib/ai_bot/entry_point.rb @@ -10,9 +10,9 @@ module DiscourseAi 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 @@ module DiscourseAi 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 @@ module DiscourseAi :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 @@ module DiscourseAi 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 @@ module DiscourseAi ) 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 @@ module DiscourseAi 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 @@ module DiscourseAi :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 @@ module DiscourseAi :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 @@ module DiscourseAi 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 @@ module DiscourseAi 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 07c4984b..d09fa2e7 100644 --- a/lib/ai_bot/playground.rb +++ b/lib/ai_bot/playground.rb @@ -15,9 +15,9 @@ module DiscourseAi # 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 @@ module DiscourseAi 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 @@ module DiscourseAi 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 @@ module DiscourseAi :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 @@ module DiscourseAi 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 @@ module DiscourseAi 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 @@ module DiscourseAi 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 @@ module DiscourseAi 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 @@ module DiscourseAi 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 @@ module DiscourseAi 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 @@ module DiscourseAi 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 @@ module DiscourseAi 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 @@ module DiscourseAi end context = - DiscourseAi::Personas::BotContext.new( + DiscourseAi::Agents::BotContext.new( participants: participants, message_id: message.id, channel_id: channel.id, @@ -324,7 +324,7 @@ module DiscourseAi 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 @@ module DiscourseAi ) 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 @@ module DiscourseAi # 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 @@ module DiscourseAi 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 @@ module DiscourseAi ) 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 @@ module DiscourseAi 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 @@ module DiscourseAi 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 cfb2fbeb..9fbd60ef 100644 --- a/lib/ai_bot/response_http_streamer.rb +++ b/lib/ai_bot/response_http_streamer.rb @@ -31,7 +31,7 @@ module DiscourseAi # this allows us to release memory earlier def queue_streamed_reply( io:, - persona:, + agent:, user:, topic:, query:, @@ -53,7 +53,7 @@ module DiscourseAi 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 @@ module DiscourseAi 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 e43bcbc5..0752590d 100644 --- a/lib/automation.rb +++ b/lib/automation.rb @@ -42,15 +42,15 @@ module DiscourseAi 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 dbbdbc6a..decb24eb 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 @@ module DiscourseAi 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 58b9210d..a8eb4eee 100644 --- a/lib/automation/llm_tool_triage.rb +++ b/lib/automation/llm_tool_triage.rb @@ -7,7 +7,7 @@ module DiscourseAi 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 20ab9ae4..05cd87de 100644 --- a/lib/automation/llm_triage.rb +++ b/lib/automation/llm_triage.rb @@ -20,11 +20,11 @@ module DiscourseAi 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 @@ module DiscourseAi 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 c115bc50..b25bdf4d 100644 --- a/lib/configuration/persona_enumerator.rb +++ b/lib/configuration/agent_enumerator.rb @@ -4,15 +4,15 @@ require "enum_site_setting" 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 200fc0a2..6fa13c32 100644 --- a/lib/configuration/llm_enumerator.rb +++ b/lib/configuration/llm_enumerator.rb @@ -16,10 +16,10 @@ module DiscourseAi 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 @@ module DiscourseAi 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 b64af15c..5ebd1b2f 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 e33e35e7..e214f002 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 @@ module DiscourseAi 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 d3b999c2..2095d9e4 100644 --- a/lib/features.rb +++ b/lib/features.rb @@ -9,7 +9,7 @@ module DiscourseAi 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 @@ module DiscourseAi 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 @@ module DiscourseAi 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 @@ module DiscourseAi 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 @@ module DiscourseAi 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 45cb1b55..7956d3cc 100644 --- a/lib/guardian_extensions.rb +++ b/lib/guardian_extensions.rb @@ -25,25 +25,25 @@ module DiscourseAi 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 a7b69763..0b2ceea6 100644 --- a/lib/summarization.rb +++ b/lib/summarization.rb @@ -5,60 +5,60 @@ module DiscourseAi 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 @@ module DiscourseAi ### 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 ca9cd6d5..e8383003 100644 --- a/lib/summarization/entry_point.rb +++ b/lib/summarization/entry_point.rb @@ -7,10 +7,10 @@ module DiscourseAi 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 17df679b..460b2904 100644 --- a/lib/summarization/fold_content.rb +++ b/lib/summarization/fold_content.rb @@ -101,7 +101,7 @@ module DiscourseAi end context = - DiscourseAi::Personas::BotContext.new( + DiscourseAi::Agents::BotContext.new( user: user, skip_tool_details: true, feature_name: strategy.feature, @@ -114,7 +114,7 @@ module DiscourseAi 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 8510dbf9..0bd79f71 100644 --- a/plugin.rb +++ b/plugin.rb @@ -39,9 +39,9 @@ register_asset "stylesheets/modules/summarization/desktop/ai-summary.scss", :des 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 @@ after_initialize do 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 @@ after_initialize do 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 00000000..6d10b6d4 --- /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 4b3c3f02..622ba68f 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 b02212a1..2cb5cf17 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 6f412e0f..fbb8e966 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 9651e0ce..78f5fe39 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 d3b1ed58..26a2beb7 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 @@ RSpec.describe Jobs::DigestRagUpload do 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 @@ RSpec.describe Jobs::DigestRagUpload do 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 @@ RSpec.describe Jobs::DigestRagUpload do 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 @@ RSpec.describe Jobs::DigestRagUpload do 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 10558745..9058ecc0 100644 --- a/spec/jobs/regular/generate_rag_embeddings_spec.rb +++ b/spec/jobs/regular/generate_rag_embeddings_spec.rb @@ -6,10 +6,10 @@ RSpec.describe Jobs::GenerateRagEmbeddings do 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 1543bb3f..3f5a7869 100644 --- a/spec/jobs/regular/stream_discord_reply_spec.rb +++ b/spec/jobs/regular/stream_discord_reply_spec.rb @@ -14,22 +14,22 @@ RSpec.describe Jobs::StreamDiscordReply, type: :job do 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 4736f03e..2d18c8d9 100644 --- a/spec/jobs/regular/stream_discover_reply_spec.rb +++ b/spec/jobs/regular/stream_discover_reply_spec.rb @@ -7,12 +7,12 @@ RSpec.describe Jobs::StreamDiscoverReply do 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 d07813ff..4403f916 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 5578f8b7..c8d6b142 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 @@ RSpec.describe DiscourseAi::Personas::Bot do 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 @@ RSpec.describe DiscourseAi::Personas::Bot do 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 @@ RSpec.describe DiscourseAi::Personas::Bot do 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 @@ RSpec.describe DiscourseAi::Personas::Bot do 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 d3e90568..090bc66b 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 @@ class TestPersona < DiscourseAi::Personas::Persona 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 @@ RSpec.describe DiscourseAi::Personas::Persona do 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 @@ RSpec.describe DiscourseAi::Personas::Persona do 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 @@ RSpec.describe DiscourseAi::Personas::Persona do ) 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 @@ RSpec.describe DiscourseAi::Personas::Persona do ) 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 @@ RSpec.describe DiscourseAi::Personas::Persona do ) 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 @@ RSpec.describe DiscourseAi::Personas::Persona do ) 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 @@ RSpec.describe DiscourseAi::Personas::Persona do ) 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 @@ RSpec.describe DiscourseAi::Personas::Persona do 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 @@ RSpec.describe DiscourseAi::Personas::Persona do 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 @@ RSpec.describe DiscourseAi::Personas::Persona do 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 @@ RSpec.describe DiscourseAi::Personas::Persona do 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 @@ RSpec.describe DiscourseAi::Personas::Persona do ] 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 @@ RSpec.describe DiscourseAi::Personas::Persona do 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 @@ RSpec.describe DiscourseAi::Personas::Persona do 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 @@ RSpec.describe DiscourseAi::Personas::Persona do 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 @@ RSpec.describe DiscourseAi::Personas::Persona do ) 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 @@ RSpec.describe DiscourseAi::Personas::Persona do 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 @@ RSpec.describe DiscourseAi::Personas::Persona do 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 @@ RSpec.describe DiscourseAi::Personas::Persona do 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 7fe54399..08af4a03 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 d216d6c0..56450d9e 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 24f15224..6204189e 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 @@ RSpec.describe DiscourseAi::Personas::SettingsExplorer do 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 d2f1614e..c1a0385f 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 @@ RSpec.describe DiscourseAi::Personas::SqlHelper do 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 929e54ef..d5224d98 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 @@ RSpec.describe DiscourseAi::Personas::Tools::CreateArtifact do { 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 0aa18fea..6fda43e5 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 50d4ab72..52628755 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 643e3fe7..7e6bb131 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 1ccc4d4d..be66646a 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 4242aec4..a18aedcb 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 4186dd01..f0693455 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 e8b3d226..cf6826ce 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 b8fbca27..14b401a2 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 cc6926fd..06def4ba 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 5062cea9..bf87bb78 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 @@ RSpec.describe DiscourseAi::Personas::Tools::Google do { 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 342c9f67..278c4c33 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 @@ RSpec.describe DiscourseAi::Personas::Tools::Image do { 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 cae05ee9..350b601c 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 bcda2123..e96029bb 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 b8f4ed5c..b20d3f1e 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 65f7c7f2..e55fb7af 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 279dd18e..26e201fc 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 @@ RSpec.describe DiscourseAi::Personas::Tools::ReadArtifact do { 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 @@ RSpec.describe DiscourseAi::Personas::Tools::ReadArtifact do { 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 @@ RSpec.describe DiscourseAi::Personas::Tools::ReadArtifact do { 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 @@ RSpec.describe DiscourseAi::Personas::Tools::ReadArtifact do { 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 @@ RSpec.describe DiscourseAi::Personas::Tools::ReadArtifact do { 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 2affc1f4..4f4986d8 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 @@ RSpec.describe DiscourseAi::Personas::Tools::Read do { 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 @@ RSpec.describe DiscourseAi::Personas::Tools::Read do { 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 23ed98a7..07b10517 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 @@ RSpec.describe DiscourseAi::Personas::Tools::Researcher do { 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 @@ RSpec.describe DiscourseAi::Personas::Tools::Researcher do { 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 @@ RSpec.describe DiscourseAi::Personas::Tools::Researcher do researcher = described_class.new( { filter: "category:research-category" }, - persona_options: { + agent_options: { "max_results" => "50", }, bot_user: bot_user, @@ -82,7 +82,7 @@ RSpec.describe DiscourseAi::Personas::Tools::Researcher do { 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 @@ RSpec.describe DiscourseAi::Personas::Tools::Researcher do }, 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 f3cd4356..2c1b0f1f 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 c28696f9..9788dc99 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 @@ RSpec.describe DiscourseAi::Personas::Tools::Search do 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 @@ RSpec.describe DiscourseAi::Personas::Tools::Search do 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 20e26b64..4c54c41f 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 2bda3cd3..4287ceb8 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 e92a32ad..b16ea593 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 5896d8e3..ce446ff1 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 3f8b7f06..c8b25379 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 @@ RSpec.describe DiscourseAi::Personas::Tools::UpdateArtifact do }, 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 @@ RSpec.describe DiscourseAi::Personas::Tools::UpdateArtifact do { 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 @@ RSpec.describe DiscourseAi::Personas::Tools::UpdateArtifact do { 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 @@ RSpec.describe DiscourseAi::Personas::Tools::UpdateArtifact do { 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 @@ RSpec.describe DiscourseAi::Personas::Tools::UpdateArtifact do { 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 @@ RSpec.describe DiscourseAi::Personas::Tools::UpdateArtifact do { 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 @@ RSpec.describe DiscourseAi::Personas::Tools::UpdateArtifact do }, 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 @@ RSpec.describe DiscourseAi::Personas::Tools::UpdateArtifact do { 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 @@ RSpec.describe DiscourseAi::Personas::Tools::UpdateArtifact do { 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 aebd4e66..15d36bf7 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 00000000..792201bc --- /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 9228e1bf..00000000 --- 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 36233f60..e8bf7bfa 100644 --- a/spec/lib/discord/bot/search_spec.rb +++ b/spec/lib/discord/bot/search_spec.rb @@ -20,7 +20,7 @@ RSpec.describe DiscourseAi::Discord::Bot::Search do 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 8ea2123e..9a5095a0 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 @@ describe DiscourseAi::Automation::LlmPersonaTriage do 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 @@ describe DiscourseAi::Automation::LlmPersonaTriage do 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 @@ describe DiscourseAi::Automation::LlmPersonaTriage do 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 @@ describe DiscourseAi::Automation::LlmPersonaTriage do 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 @@ describe DiscourseAi::Automation::LlmPersonaTriage do 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 8baa8dd0..0df43787 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 @@ RSpec.describe DiscourseAi::Automation::LlmToolTriage do 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 @@ RSpec.describe DiscourseAi::Automation::LlmToolTriage do }; } - 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 @@ RSpec.describe DiscourseAi::Automation::LlmToolTriage do 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 1b3ca690..af92bb63 100644 --- a/spec/lib/discourse_automation/llm_triage_spec.rb +++ b/spec/lib/discourse_automation/llm_triage_spec.rb @@ -5,7 +5,7 @@ return if !defined?(DiscourseAutomation) 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 @@ describe DiscourseAi::Automation::LlmTriage do 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 @@ describe DiscourseAi::Automation::LlmTriage do # 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 @@ describe DiscourseAi::Automation::LlmTriage do 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 @@ describe DiscourseAi::Automation::LlmTriage do 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 @@ describe DiscourseAi::Automation::LlmTriage do 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 @@ describe DiscourseAi::Automation::LlmTriage do 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 @@ describe DiscourseAi::Automation::LlmTriage do # 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 33d43c45..859ce2dd 100644 --- a/spec/lib/guardian_extensions_spec.rb +++ b/spec/lib/guardian_extensions_spec.rb @@ -17,7 +17,7 @@ describe DiscourseAi::GuardianExtensions do 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 @@ describe DiscourseAi::GuardianExtensions do 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 @@ describe DiscourseAi::GuardianExtensions do 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 @@ describe DiscourseAi::GuardianExtensions do 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 @@ describe DiscourseAi::GuardianExtensions do 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 0e1aac3e..0d32161b 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 @@ RSpec.describe DiscourseAi::AiBot::EntryPoint do 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 @@ RSpec.describe DiscourseAi::AiBot::EntryPoint do 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 7808f1f9..46925251 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 @@ RSpec.describe Jobs::CreateAiReply do "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 e9ad0ac3..50746593 100644 --- a/spec/lib/modules/ai_bot/playground_spec.rb +++ b/spec/lib/modules/ai_bot/playground_spec.rb @@ -20,12 +20,12 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do ) 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do ) 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do message = ChatSDK::Message.create( channel_id: channel.id, - raw: "Hello @#{persona.user.username}", + raw: "Hello @#{agent.user.username}", guardian: guardian, ) @@ -418,7 +418,7 @@ RSpec.describe DiscourseAi::AiBot::Playground do ) 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do end it "can run tools" do - persona.update!(tools: ["Time"]) + agent.update!(tools: ["Time"]) tool_call1 = DiscourseAi::Completions::ToolCall.new( @@ -535,8 +535,8 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground 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?", post_type: Post.types[:whisper], ) end @@ -607,36 +607,36 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do 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 @@ RSpec.describe DiscourseAi::AiBot::Playground do ) 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 f63da694..8c8b8fcd 100644 --- a/spec/lib/modules/sentiment/post_classification_spec.rb +++ b/spec/lib/modules/sentiment/post_classification_spec.rb @@ -127,7 +127,7 @@ RSpec.describe DiscourseAi::Sentiment::PostClassification do 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 0d57cbf9..9eb80e85 100644 --- a/spec/lib/modules/summarization/entry_point_spec.rb +++ b/spec/lib/modules/summarization/entry_point_spec.rb @@ -63,7 +63,7 @@ RSpec.describe DiscourseAi::Summarization::EntryPoint do 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 866d63e8..2869e5ea 100644 --- a/spec/lib/utils/research/filter_spec.rb +++ b/spec/lib/utils/research/filter_spec.rb @@ -4,7 +4,7 @@ describe DiscourseAi::Utils::Research::Filter do 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 00000000..a49033eb --- /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 0e6b9d13..c10d1337 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 @@ RSpec.describe AiPersona do 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 @@ RSpec.describe AiPersona do 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 @@ RSpec.describe AiPersona do 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 @@ RSpec.describe AiPersona do 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 @@ RSpec.describe AiPersona do 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 @@ RSpec.describe AiPersona do 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 @@ RSpec.describe AiPersona do 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 @@ RSpec.describe AiPersona do 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 @@ RSpec.describe AiPersona do ) 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 @@ RSpec.describe AiPersona do 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 @@ RSpec.describe AiPersona do 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 @@ RSpec.describe AiPersona do 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 84670303..00000000 --- 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 56de5de5..a15bc981 100644 --- a/spec/models/ai_tool_spec.rb +++ b/spec/models/ai_tool_spec.rb @@ -121,7 +121,7 @@ RSpec.describe AiTool do }, ) - 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 @@ RSpec.describe AiTool do 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 @@ RSpec.describe AiTool do 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 @@ RSpec.describe AiTool do ) 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 @@ RSpec.describe AiTool do 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 @@ RSpec.describe AiTool do 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 58824c47..5cdb8d2f 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 @@ RSpec.describe RagDocumentFragment do 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 @@ RSpec.describe RagDocumentFragment do 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 @@ RSpec.describe RagDocumentFragment do 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 @@ RSpec.describe RagDocumentFragment do 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 @@ RSpec.describe RagDocumentFragment do 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 @@ RSpec.describe RagDocumentFragment do 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 @@ RSpec.describe RagDocumentFragment do 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 34121ab9..7d40065f 100644 --- a/spec/models/user_option_spec.rb +++ b/spec/models/user_option_spec.rb @@ -4,8 +4,8 @@ RSpec.describe UserOption do 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 @@ RSpec.describe UserOption do 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 0346df7c..810f7b9e 100644 --- a/spec/plugin_helper.rb +++ b/spec/plugin_helper.rb @@ -16,8 +16,8 @@ module DiscourseAi::ChatBotHelper 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 17b7fe34..3f27b390 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 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do 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 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do 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 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do }, ) - 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 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do 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 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do 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 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do 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 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do } 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"] + agent_json = response.parsed_body["ai_agent"] - 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( + 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 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do 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 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do 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 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do } 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 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do 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 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do } 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 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do 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 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do 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 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do 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 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do 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 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do 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 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do 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 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do 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 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do 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 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do 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 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do 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 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do # 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 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do 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 8265d856..799e88ce 100644 --- a/spec/requests/admin/ai_features_controller_spec.rb +++ b/spec/requests/admin/ai_features_controller_spec.rb @@ -5,8 +5,8 @@ RSpec.describe DiscourseAi::Admin::AiFeaturesController do 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 @@ RSpec.describe DiscourseAi::Admin::AiFeaturesController do 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 280204f2..4b6c53e2 100644 --- a/spec/requests/admin/ai_llms_controller_spec.rb +++ b/spec/requests/admin/ai_llms_controller_spec.rb @@ -11,10 +11,10 @@ RSpec.describe DiscourseAi::Admin::AiLlmsController do 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 @@ RSpec.describe DiscourseAi::Admin::AiLlmsController do 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 @@ RSpec.describe DiscourseAi::Admin::AiLlmsController do 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 24b4b387..aee074b2 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 @@ RSpec.describe DiscourseAi::Admin::RagDocumentFragmentsController do 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 385c37e0..d2a6d108 100644 --- a/spec/requests/ai_bot/bot_controller_spec.rb +++ b/spec/requests/ai_bot/bot_controller_spec.rb @@ -127,9 +127,9 @@ RSpec.describe DiscourseAi::AiBot::BotController do 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 @@ RSpec.describe DiscourseAi::AiBot::BotController do 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 @@ RSpec.describe DiscourseAi::AiBot::BotController do 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 @@ RSpec.describe DiscourseAi::AiBot::BotController do 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 00000000..5663fe9a --- /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 677e2743..00000000 --- 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 00000000..d3d2b51e --- /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 613cd78c..51c068a0 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 @@ RSpec.describe "Admin AI features configuration", type: :system, js: true do 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 @@ RSpec.describe "Admin AI features configuration", type: :system, js: true do 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 add89b4d..00000000 --- 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 00000000..31e38c4d --- /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 b777c45a..1f44adf6 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 @@ RSpec.describe "AI chat channel summarization", type: :system, js: true do 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 @@ RSpec.describe "AI chat channel summarization", type: :system, js: true do 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 3510c32f..8428edde 100644 --- a/spec/system/ai_bot/homepage_spec.rb +++ b/spec/system/ai_bot/homepage_spec.rb @@ -36,12 +36,12 @@ RSpec.describe "AI Bot - Homepage", type: :system do 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 @@ RSpec.describe "AI Bot - Homepage", type: :system do 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 @@ RSpec.describe "AI Bot - Homepage", type: :system do 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 1cf5e231..00000000 --- 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 9e8c6803..43a99a26 100644 --- a/spec/system/ai_bot/share_spec.rb +++ b/spec/system/ai_bot/share_spec.rb @@ -43,14 +43,14 @@ RSpec.describe "Share conversation", type: :system do 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 b1bc90fd..6cd30f75 100644 --- a/spec/system/ai_bot/tool_spec.rb +++ b/spec/system/ai_bot/tool_spec.rb @@ -65,7 +65,7 @@ describe "AI Tool Management", type: :system do 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 12a1109d..b12de867 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 @@ RSpec.describe "AI Post helper", type: :system, js: true do 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 69b93af3..ceee2b64 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 @@ module PageObjects 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 @@ module PageObjects ).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 e6ad9efd..dcf374cb 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 @@ module PageObjects 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 dbb78417..3f5b8050 100644 --- a/spec/system/summarization/chat_summarization_spec.rb +++ b/spec/system/summarization/chat_summarization_spec.rb @@ -12,7 +12,7 @@ RSpec.describe "Summarize a channel since your last visit", type: :system do 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 30b147a0..a9fccd32 100644 --- a/spec/system/summarization/topic_summarization_spec.rb +++ b/spec/system/summarization/topic_summarization_spec.rb @@ -24,7 +24,7 @@ RSpec.describe "Summarize a topic ", type: :system do 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 a21d51a2..c4c83484 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], ]); });