mirror of
https://github.com/discourse/discourse.git
synced 2025-06-05 14:07:30 +08:00
FIX: cancel fetching messages after channel change (#21689)
This issue was especially visible in tests. the `@debounce(100)` was not cancelled when changing channel which was causing 404s as we were trying to load messages on a channel which was deleted as the channel has been destroyed at the end of the test. This is still not a perfect solution, as we can only cancel the start of `fetchMessages`, but we can't cancel the actual `chatApi.channel` request which result can potentially happens after channel changed, which we try to mitigate with various checks on to ensure visible channel == loaded messages channel. This commit also tries to make handler naming and cancelling more consistent. <!-- NOTE: All pull requests should have tests (rspec in Ruby, qunit in JavaScript). If your code does not include test coverage, please include an explanation of why it was omitted. -->
This commit is contained in:
@ -20,6 +20,7 @@ import {
|
|||||||
} from "discourse/lib/user-presence";
|
} from "discourse/lib/user-presence";
|
||||||
import isZoomed from "discourse/plugins/chat/discourse/lib/zoom-check";
|
import isZoomed from "discourse/plugins/chat/discourse/lib/zoom-check";
|
||||||
import { tracked } from "@glimmer/tracking";
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
import discourseDebounce from "discourse-common/lib/debounce";
|
||||||
|
|
||||||
const PAGE_SIZE = 50;
|
const PAGE_SIZE = 50;
|
||||||
const PAST = "past";
|
const PAST = "past";
|
||||||
@ -83,11 +84,11 @@ export default class ChatLivePane extends Component {
|
|||||||
|
|
||||||
@action
|
@action
|
||||||
teardownListeners() {
|
teardownListeners() {
|
||||||
|
this.#cancelHandlers();
|
||||||
document.removeEventListener("scroll", this._forceBodyScroll);
|
document.removeEventListener("scroll", this._forceBodyScroll);
|
||||||
removeOnPresenceChange(this.onPresenceChangeCallback);
|
removeOnPresenceChange(this.onPresenceChangeCallback);
|
||||||
this.unsubscribeToUpdates(this._loadedChannelId);
|
this.unsubscribeToUpdates(this._loadedChannelId);
|
||||||
this.requestedTargetMessageId = null;
|
this.requestedTargetMessageId = null;
|
||||||
cancel(this._laterComputeHandler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@ -104,6 +105,8 @@ export default class ChatLivePane extends Component {
|
|||||||
|
|
||||||
@action
|
@action
|
||||||
updateChannel() {
|
updateChannel() {
|
||||||
|
this.#cancelHandlers();
|
||||||
|
|
||||||
this.loadedOnce = false;
|
this.loadedOnce = false;
|
||||||
|
|
||||||
// Technically we could keep messages to avoid re-fetching them, but
|
// Technically we could keep messages to avoid re-fetching them, but
|
||||||
@ -138,7 +141,7 @@ export default class ChatLivePane extends Component {
|
|||||||
if (this.requestedTargetMessageId) {
|
if (this.requestedTargetMessageId) {
|
||||||
this.highlightOrFetchMessage(this.requestedTargetMessageId);
|
this.highlightOrFetchMessage(this.requestedTargetMessageId);
|
||||||
} else {
|
} else {
|
||||||
this.fetchMessages({ fetchFromLastMessage: false });
|
this.debounceFetchMessages({ fetchFromLastMessage: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,7 +152,15 @@ export default class ChatLivePane extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@debounce(100)
|
debounceFetchMessages(options) {
|
||||||
|
this._debounceFetchMessagesHandler = discourseDebounce(
|
||||||
|
this,
|
||||||
|
this.fetchMessages,
|
||||||
|
options,
|
||||||
|
100
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fetchMessages(options = {}) {
|
fetchMessages(options = {}) {
|
||||||
if (this._selfDeleted) {
|
if (this._selfDeleted) {
|
||||||
return;
|
return;
|
||||||
@ -292,9 +303,7 @@ export default class ChatLivePane extends Component {
|
|||||||
this.scrollToMessage(messages[0].id, { position: "end" });
|
this.scrollToMessage(messages[0].id, { position: "end" });
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(this._handleErrors)
|
||||||
this._handleErrors();
|
|
||||||
})
|
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this[loadingMoreKey] = false;
|
this[loadingMoreKey] = false;
|
||||||
this.fillPaneAttempt();
|
this.fillPaneAttempt();
|
||||||
@ -386,7 +395,7 @@ export default class ChatLivePane extends Component {
|
|||||||
});
|
});
|
||||||
this.requestedTargetMessageId = null;
|
this.requestedTargetMessageId = null;
|
||||||
} else {
|
} else {
|
||||||
this.fetchMessages();
|
this.debounceFetchMessages();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -518,7 +527,7 @@ export default class ChatLivePane extends Component {
|
|||||||
|
|
||||||
@action
|
@action
|
||||||
computeScrollState() {
|
computeScrollState() {
|
||||||
cancel(this.onScrollEndedHandler);
|
cancel(this._onScrollEndedHandler);
|
||||||
|
|
||||||
if (!this.scrollable) {
|
if (!this.scrollable) {
|
||||||
return;
|
return;
|
||||||
@ -536,7 +545,11 @@ export default class ChatLivePane extends Component {
|
|||||||
this.onScrollEnded();
|
this.onScrollEnded();
|
||||||
} else {
|
} else {
|
||||||
this.isScrolling = true;
|
this.isScrolling = true;
|
||||||
this.onScrollEndedHandler = discourseLater(this, this.onScrollEnded, 150);
|
this._onScrollEndedHandler = discourseLater(
|
||||||
|
this,
|
||||||
|
this.onScrollEnded,
|
||||||
|
150
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -815,17 +828,29 @@ export default class ChatLivePane extends Component {
|
|||||||
|
|
||||||
_fetchAndScrollToLatest() {
|
_fetchAndScrollToLatest() {
|
||||||
this.loadedOnce = false;
|
this.loadedOnce = false;
|
||||||
return this.fetchMessages({
|
return this.debounceFetchMessages({
|
||||||
fetchFromLastMessage: true,
|
fetchFromLastMessage: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
_handleErrors(error) {
|
_handleErrors(error) {
|
||||||
switch (error?.jqXHR?.status) {
|
switch (error?.jqXHR?.status) {
|
||||||
case 429:
|
case 429:
|
||||||
case 404:
|
|
||||||
popupAjaxError(error);
|
popupAjaxError(error);
|
||||||
break;
|
break;
|
||||||
|
case 404:
|
||||||
|
// avoids handling 404 errors from a channel
|
||||||
|
// that is not the current one, this is very likely in tests
|
||||||
|
// which will destroy the channel after the test is done
|
||||||
|
if (
|
||||||
|
this.args.channel?.id &&
|
||||||
|
error.jqXHR?.requestedUrl ===
|
||||||
|
`/chat/api/channels/${this.args.channel.id}`
|
||||||
|
) {
|
||||||
|
popupAjaxError(error);
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@ -1015,4 +1040,10 @@ export default class ChatLivePane extends Component {
|
|||||||
// - 5.0 to account for rounding errors, especially on firefox
|
// - 5.0 to account for rounding errors, especially on firefox
|
||||||
return rect.bottom - 5.0 <= containerRect.bottom;
|
return rect.bottom - 5.0 <= containerRect.bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#cancelHandlers() {
|
||||||
|
cancel(this._onScrollEndedHandler);
|
||||||
|
cancel(this._laterComputeHandler);
|
||||||
|
cancel(this._debounceFetchMessagesHandler);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,10 +24,9 @@ export default class ChatMessagesManager {
|
|||||||
message.manager = this;
|
message.manager = this;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.messages = this.messages
|
this.messages = new TrackedArray(
|
||||||
.concat(messages)
|
this.messages.concat(messages).uniqBy("id").sortBy("createdAt")
|
||||||
.uniqBy("id")
|
);
|
||||||
.sortBy("createdAt");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
findMessage(messageId) {
|
findMessage(messageId) {
|
||||||
|
@ -8,8 +8,8 @@ RSpec.describe "Info pages", type: :system, js: true do
|
|||||||
|
|
||||||
before do
|
before do
|
||||||
chat_system_bootstrap
|
chat_system_bootstrap
|
||||||
sign_in(current_user)
|
|
||||||
channel_1.add(current_user)
|
channel_1.add(current_user)
|
||||||
|
sign_in(current_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when visiting from browse page" do
|
context "when visiting from browse page" do
|
||||||
|
@ -83,10 +83,6 @@ RSpec.describe "Move message to channel", type: :system, js: true do
|
|||||||
find("[data-value='#{channel_2.id}']").click
|
find("[data-value='#{channel_2.id}']").click
|
||||||
click_button(I18n.t("js.chat.move_to_channel.confirm_move"))
|
click_button(I18n.t("js.chat.move_to_channel.confirm_move"))
|
||||||
|
|
||||||
expect(page).to have_content(message_1.message)
|
|
||||||
|
|
||||||
chat.visit_channel(channel_1)
|
|
||||||
|
|
||||||
expect(channel).to have_deleted_message(message_1)
|
expect(channel).to have_deleted_message(message_1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Reference in New Issue
Block a user