FIX: Don't advance draft sequence when editing topic title (#16002)

This commit handles the edge case where a draft is lost with no warnings if the user edits the title (or category/tags) of a topic while they're replying.to the same topic. Repro steps are as follows:

1. Start replying to a topic and type enough to get a draft saved.
2. Scroll up to the topic title and click the pencil icon next to the topic title, change the title, category and/or tags, and then save the changes.
3. Reload the page and you'll see that the draft is gone.

This happens because we only allow 1 draft per topic per user and when you edit the title of a topic that you're replying to, from the server perspective it'll look like as if you've submitted your reply so it will advance the draft sequence for the topic and delete the draft.

The fix in this commit makes `PostRevisor` skip advancing the draft sequence when a topic's title is edited using the pencil button next to the title.

Internal ticket: t60854.

Co-authored-by: Robin Ward <robin.ward@gmail.com>
This commit is contained in:
Osama Sayegh 2022-02-23 10:39:54 +03:00 committed by GitHub
parent 799e27d15d
commit 586d572e05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 52 additions and 7 deletions

View File

@ -980,7 +980,7 @@ export default Controller.extend(bufferedProperty("model"), {
// save the modifications // save the modifications
const props = this.get("buffered.buffer"); const props = this.get("buffered.buffer");
Topic.update(this.model, props) Topic.update(this.model, props, { fastEdit: true })
.then(() => { .then(() => {
// We roll back on success here because `update` saves the properties to the topic // We roll back on success here because `update` saves the properties to the topic
this.rollbackBuffer(); this.rollbackBuffer();

View File

@ -661,7 +661,7 @@ Topic.reopenClass({
} }
}, },
update(topic, props) { update(topic, props, opts = {}) {
// We support `category_id` and `categoryId` for compatibility // We support `category_id` and `categoryId` for compatibility
if (typeof props.categoryId !== "undefined") { if (typeof props.categoryId !== "undefined") {
props.category_id = props.categoryId; props.category_id = props.categoryId;
@ -673,9 +673,13 @@ Topic.reopenClass({
delete props.category_id; delete props.category_id;
} }
const data = { ...props };
if (opts.fastEdit) {
data.keep_existing_draft = true;
}
return ajax(topic.get("url"), { return ajax(topic.get("url"), {
type: "PUT", type: "PUT",
data: JSON.stringify(props), data: JSON.stringify(data),
contentType: "application/json", contentType: "application/json",
}).then((result) => { }).then((result) => {
// The title can be cleaned up server side // The title can be cleaned up server side

View File

@ -397,7 +397,13 @@ class TopicsController < ApplicationController
bypass_bump = should_bypass_bump?(changes) bypass_bump = should_bypass_bump?(changes)
first_post = topic.ordered_posts.first first_post = topic.ordered_posts.first
success = PostRevisor.new(first_post, topic).revise!(current_user, changes, validate_post: false, bypass_bump: bypass_bump) success = PostRevisor.new(first_post, topic).revise!(
current_user,
changes,
validate_post: false,
bypass_bump: bypass_bump,
keep_existing_draft: params[:keep_existing_draft].to_s == "true"
)
if !success && topic.errors.blank? if !success && topic.errors.blank?
topic.errors.add(:base, :unable_to_update) topic.errors.add(:base, :unable_to_update)

View File

@ -159,7 +159,16 @@ class PostRevisor
end end
end end
return false unless should_revise? if !should_revise?
# the draft sequence is advanced here to handle the edge case where a
# user opens the composer to edit a post and makes some changes (which
# saves a draft), but then un-does the changes and clicks save. In this
# case, should_revise? returns false because nothing has really changed
# in the post, but we want to get rid of the draft so we advance the
# sequence.
advance_draft_sequence if !opts[:keep_existing_draft]
return false
end
@post.acting_user = @editor @post.acting_user = @editor
@topic.acting_user = @editor @topic.acting_user = @editor
@ -204,7 +213,7 @@ class PostRevisor
plugin_callbacks plugin_callbacks
revise_topic revise_topic
advance_draft_sequence advance_draft_sequence if !opts[:keep_existing_draft]
end end
# Lock the post by default if the appropriate setting is true # Lock the post by default if the appropriate setting is true
@ -271,7 +280,6 @@ class PostRevisor
return true return true
end end
end end
advance_draft_sequence
false false
end end

View File

@ -1236,6 +1236,33 @@ describe PostRevisor do
end end
end end
end end
context 'with drafts' do
it "does not advance draft sequence if keep_existing_draft option is true" do
post = Fabricate(:post, user: user)
topic = post.topic
draft_key = "topic_#{topic.id}"
data = { reply: "test 12222" }.to_json
Draft.set(user, draft_key, 0, data)
Draft.set(user, draft_key, 0, data)
expect {
PostRevisor.new(post).revise!(
post.user,
{ title: "updated title for my topic" },
keep_existing_draft: true
)
}.to change { Draft.where(user: user, draft_key: draft_key).first.sequence }.by(0)
.and change { DraftSequence.where(user_id: user.id, draft_key: draft_key).first.sequence }.by(0)
expect {
PostRevisor.new(post).revise!(
post.user,
{ title: "updated title for my topic" },
)
}.to change { Draft.where(user: user, draft_key: draft_key).count }.from(1).to(0)
.and change { DraftSequence.where(user_id: user.id, draft_key: draft_key).first.sequence }.by(1)
end
end
end end
context 'when the review_every_post setting is enabled' do context 'when the review_every_post setting is enabled' do