mirror of
https://github.com/discourse/discourse.git
synced 2025-06-06 03:06:53 +08:00
PERF: cook message in background (#24227)
This commit starts from a simple observation: cooking messages on the hot path can be slow. Especially with a lot of mentions. To move cooking from the hot path, this commit has made the following changes: - updating cooked, inserting mentions and notifying user of new mentions has been moved inside the `process_message` job. It happens right after the `Chat::MessageProcessor` run, which is where the cooking happens. - the similar existing code in `rebake!` has also been moved to rely on the `process_message`job only - refactored `create_mentions` and `update_mentions` into one single `upsert_mentions` which can be called invariably - allows services to decide if their job is ran inline or later. It avoids to need to know you have to use `Jobs.run_immediately!` in this case, in tests it will be inline per default - made various frontend changes to make the chat-channel component lifecycle clearer. we had to handle `did-update @channel` which was super awkward and creating bugs with listeners which the changes of the PR made clear in failing specs - adds a new `-processed` (and `-not-processed`) class on the chat message, this is made to have a good lifecyle hook in system specs
This commit is contained in:
@ -8,9 +8,7 @@
|
||||
{{did-insert this.setUploadDropZone}}
|
||||
{{did-insert this.setupListeners}}
|
||||
{{will-destroy this.teardownListeners}}
|
||||
{{did-update this.loadMessages @targetMessageId}}
|
||||
{{did-insert this.didUpdateChannel}}
|
||||
{{did-update this.didUpdateChannel @channel.id}}
|
||||
{{did-insert this.addAutoFocusEventListener}}
|
||||
{{will-destroy this.removeAutoFocusEventListener}}
|
||||
data-id={{@channel.id}}
|
||||
|
@ -61,7 +61,6 @@ export default class ChatChannel extends Component {
|
||||
@tracked isScrolling = false;
|
||||
|
||||
scrollable = null;
|
||||
_loadedChannelId = null;
|
||||
_mentionWarningsSeen = {};
|
||||
_unreachableGroupMentions = [];
|
||||
_overMembersLimitGroupMentions = [];
|
||||
@ -98,7 +97,7 @@ export default class ChatChannel extends Component {
|
||||
teardownListeners() {
|
||||
this.#cancelHandlers();
|
||||
removeOnPresenceChange(this.onPresenceChangeCallback);
|
||||
this.unsubscribeToUpdates(this._loadedChannelId);
|
||||
this.unsubscribeToUpdates(this.args.channel.id);
|
||||
}
|
||||
|
||||
@action
|
||||
@ -109,12 +108,6 @@ export default class ChatChannel extends Component {
|
||||
|
||||
@action
|
||||
didUpdateChannel() {
|
||||
this.#cancelHandlers();
|
||||
|
||||
if (!this.args.channel) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.messagesManager.clear();
|
||||
|
||||
if (
|
||||
@ -124,12 +117,6 @@ export default class ChatChannel extends Component {
|
||||
this.chatChannelsManager.follow(this.args.channel);
|
||||
}
|
||||
|
||||
if (this._loadedChannelId !== this.args.channel.id) {
|
||||
this.unsubscribeToUpdates(this._loadedChannelId);
|
||||
this.pane.selectingMessages = false;
|
||||
this._loadedChannelId = this.args.channel.id;
|
||||
}
|
||||
|
||||
const existingDraft = this.chatDraftsManager.get({
|
||||
channelId: this.args.channel.id,
|
||||
});
|
||||
@ -647,7 +634,6 @@ export default class ChatChannel extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
this.unsubscribeToUpdates(channel.id);
|
||||
this.messageBus.subscribe(
|
||||
`/chat/${channel.id}`,
|
||||
this.onMessage,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import Component from "@ember/component";
|
||||
import { action } from "@ember/object";
|
||||
import { cancel, throttle } from "@ember/runloop";
|
||||
import { cancel, next, throttle } from "@ember/runloop";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import DiscourseURL from "discourse/lib/url";
|
||||
@ -188,11 +188,13 @@ export default Component.extend({
|
||||
},
|
||||
|
||||
@action
|
||||
openInFullPage() {
|
||||
async openInFullPage() {
|
||||
this.chatStateManager.storeAppURL();
|
||||
this.chatStateManager.prefersFullPage();
|
||||
this.chat.activeChannel = null;
|
||||
|
||||
await new Promise((resolve) => next(resolve));
|
||||
|
||||
return DiscourseURL.routeTo(this.chatStateManager.lastKnownChatURL);
|
||||
},
|
||||
|
||||
|
@ -16,10 +16,12 @@
|
||||
{{did-update this.fetchChannel @params.channelId}}
|
||||
>
|
||||
{{#if this.chat.activeChannel}}
|
||||
<ChatChannel
|
||||
@targetMessageId={{readonly @params.messageId}}
|
||||
@channel={{this.chat.activeChannel}}
|
||||
/>
|
||||
{{#each (array this.chat.activeChannel) as |channel|}}
|
||||
<ChatChannel
|
||||
@targetMessageId={{readonly @params.messageId}}
|
||||
@channel={{channel}}
|
||||
/>
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
@ -509,6 +509,7 @@ export default class ChatMessage extends Component {
|
||||
(if @message.highlighted "-highlighted")
|
||||
(if (eq @message.user.id this.currentUser.id) "is-by-current-user")
|
||||
(if @message.staged "-staged" "-persisted")
|
||||
(if @message.processed "-processed" "-not-processed")
|
||||
(if this.hasActiveState "-active")
|
||||
(if @message.bookmark "-bookmarked")
|
||||
(if @message.deletedAt "-deleted")
|
||||
|
@ -1,3 +1,3 @@
|
||||
{{#if @channel.id}}
|
||||
<ChatChannel @channel={{@channel}} @targetMessageId={{@targetMessageId}} />
|
||||
{{/if}}
|
||||
{{#each (array @channel) as |channel|}}
|
||||
<ChatChannel @channel={{channel}} @targetMessageId={{@targetMessageId}} />
|
||||
{{/each}}
|
@ -192,6 +192,7 @@ export default class ChatChannel {
|
||||
async stageMessage(message) {
|
||||
message.id = guid();
|
||||
message.staged = true;
|
||||
message.processed = false;
|
||||
message.draft = false;
|
||||
message.createdAt = new Date();
|
||||
message.channel = this;
|
||||
|
@ -26,6 +26,7 @@ export default class ChatMessage {
|
||||
@tracked selected;
|
||||
@tracked channel;
|
||||
@tracked staged;
|
||||
@tracked processed;
|
||||
@tracked draftSaved;
|
||||
@tracked draft;
|
||||
@tracked createdAt;
|
||||
@ -64,6 +65,7 @@ export default class ChatMessage {
|
||||
this.draftSaved = args.draftSaved || args.draft_saved || false;
|
||||
this.firstOfResults = args.firstOfResults || args.first_of_results || false;
|
||||
this.staged = args.staged || false;
|
||||
this.processed = args.processed || true;
|
||||
this.edited = args.edited || false;
|
||||
this.editing = args.editing || false;
|
||||
this.availableFlags = args.availableFlags || args.available_flags;
|
||||
|
@ -61,6 +61,7 @@ export default class ChatThread {
|
||||
async stageMessage(message) {
|
||||
message.id = guid();
|
||||
message.staged = true;
|
||||
message.processed = false;
|
||||
message.draft = false;
|
||||
message.createdAt ??= moment.utc().format();
|
||||
message.thread = this;
|
||||
|
@ -16,7 +16,6 @@ export function handleStagedMessage(channel, messagesManager, data) {
|
||||
stagedMessage.excerpt = data.chat_message.excerpt;
|
||||
stagedMessage.channel = channel;
|
||||
stagedMessage.createdAt = new Date(data.chat_message.created_at);
|
||||
stagedMessage.cooked = data.chat_message.cooked;
|
||||
|
||||
return stagedMessage;
|
||||
}
|
||||
@ -131,6 +130,7 @@ export default class ChatPaneBaseSubscriptionsManager extends Service {
|
||||
const message = this.messagesManager.findMessage(data.chat_message.id);
|
||||
if (message) {
|
||||
message.cooked = data.chat_message.cooked;
|
||||
message.processed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -144,8 +144,6 @@ export default class ChatPaneBaseSubscriptionsManager extends Service {
|
||||
handleEditMessage(data) {
|
||||
const message = this.messagesManager.findMessage(data.chat_message.id);
|
||||
if (message) {
|
||||
message.message = data.chat_message.message;
|
||||
message.cooked = data.chat_message.cooked;
|
||||
message.excerpt = data.chat_message.excerpt;
|
||||
message.uploads = cloneJSON(data.chat_message.uploads || []);
|
||||
message.edited = data.chat_message.edited;
|
||||
|
Reference in New Issue
Block a user