2019-05-12 23:04:27 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2022-12-23 15:36:08 -05:00
|
|
|
require "rails_helper"
|
2017-05-22 05:09:40 -04:00
|
|
|
|
|
|
|
RSpec.describe "Managing Posts solved status" do
|
|
|
|
let(:topic) { Fabricate(:topic) }
|
2023-06-20 10:52:02 -04:00
|
|
|
fab!(:user) { Fabricate(:trust_level_4) }
|
2017-05-22 05:09:40 -04:00
|
|
|
let(:p1) { Fabricate(:post, topic: topic) }
|
|
|
|
|
2022-12-23 15:36:08 -05:00
|
|
|
before { SiteSetting.allow_solved_on_all_topics = true }
|
2017-05-22 05:09:40 -04:00
|
|
|
|
2024-04-26 10:17:50 -04:00
|
|
|
describe "customer filters" do
|
|
|
|
before do
|
|
|
|
SiteSetting.allow_solved_on_all_topics = false
|
|
|
|
SiteSetting.enable_solved_tags = solvable_tag.name
|
|
|
|
end
|
|
|
|
|
|
|
|
fab!(:solvable_category) do
|
|
|
|
category = Fabricate(:category)
|
|
|
|
|
|
|
|
CategoryCustomField.create(
|
|
|
|
category_id: category.id,
|
|
|
|
name: ::DiscourseSolved::ENABLE_ACCEPTED_ANSWERS_CUSTOM_FIELD,
|
|
|
|
value: "true",
|
|
|
|
)
|
|
|
|
|
|
|
|
category
|
|
|
|
end
|
|
|
|
|
|
|
|
fab!(:solvable_tag) { Fabricate(:tag) }
|
|
|
|
|
|
|
|
fab!(:solved_in_category) do
|
|
|
|
Fabricate(
|
|
|
|
:custom_topic,
|
|
|
|
category: solvable_category,
|
|
|
|
custom_topic_name: ::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD,
|
|
|
|
value: "42",
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
fab!(:solved_in_tag) do
|
|
|
|
Fabricate(
|
|
|
|
:custom_topic,
|
|
|
|
tags: [solvable_tag],
|
|
|
|
custom_topic_name: ::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD,
|
|
|
|
value: "42",
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
fab!(:unsolved_in_category) { Fabricate(:topic, category: solvable_category) }
|
|
|
|
fab!(:unsolved_in_tag) { Fabricate(:topic, tags: [solvable_tag]) }
|
|
|
|
|
|
|
|
fab!(:unsolved_topic) { Fabricate(:topic) }
|
|
|
|
|
|
|
|
it "can filter by solved status" do
|
|
|
|
expect(
|
|
|
|
TopicsFilter
|
|
|
|
.new(guardian: Guardian.new)
|
|
|
|
.filter_from_query_string("status:solved")
|
|
|
|
.pluck(:id),
|
|
|
|
).to contain_exactly(solved_in_category.id, solved_in_tag.id)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "can filter by unsolved status" do
|
|
|
|
expect(
|
|
|
|
TopicsFilter
|
|
|
|
.new(guardian: Guardian.new)
|
|
|
|
.filter_from_query_string("status:unsolved")
|
|
|
|
.pluck(:id),
|
|
|
|
).to contain_exactly(unsolved_in_category.id, unsolved_in_tag.id)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-05-02 21:56:10 -04:00
|
|
|
describe "search" do
|
|
|
|
before { SearchIndexer.enable }
|
|
|
|
|
|
|
|
after { SearchIndexer.disable }
|
|
|
|
|
|
|
|
it "can prioritize solved topics in search" do
|
|
|
|
normal_post =
|
|
|
|
Fabricate(
|
|
|
|
:post,
|
|
|
|
raw: "My reply carrot",
|
|
|
|
topic: Fabricate(:topic, title: "A topic that is not solved but open"),
|
|
|
|
)
|
|
|
|
|
|
|
|
solved_post =
|
|
|
|
Fabricate(
|
|
|
|
:post,
|
|
|
|
raw: "My solution carrot",
|
|
|
|
topic: Fabricate(:topic, title: "A topic that will be closed", closed: true),
|
|
|
|
)
|
|
|
|
|
|
|
|
DiscourseSolved.accept_answer!(solved_post, Discourse.system_user)
|
|
|
|
|
|
|
|
result = Search.execute("carrot")
|
|
|
|
expect(result.posts.pluck(:id)).to eq([normal_post.id, solved_post.id])
|
|
|
|
|
|
|
|
SiteSetting.prioritize_solved_topics_in_search = true
|
|
|
|
|
|
|
|
result = Search.execute("carrot")
|
|
|
|
expect(result.posts.pluck(:id)).to eq([solved_post.id, normal_post.id])
|
|
|
|
end
|
2023-06-20 10:52:02 -04:00
|
|
|
|
|
|
|
describe "#advanced_search" do
|
|
|
|
fab!(:category_enabled) do
|
|
|
|
category = Fabricate(:category)
|
|
|
|
category_custom_field =
|
|
|
|
CategoryCustomField.new(
|
|
|
|
category_id: category.id,
|
|
|
|
name: ::DiscourseSolved::ENABLE_ACCEPTED_ANSWERS_CUSTOM_FIELD,
|
|
|
|
value: "true",
|
|
|
|
)
|
|
|
|
category_custom_field.save
|
|
|
|
category
|
|
|
|
end
|
|
|
|
fab!(:category_disabled) do
|
|
|
|
category = Fabricate(:category)
|
|
|
|
category_custom_field =
|
|
|
|
CategoryCustomField.new(
|
|
|
|
category_id: category.id,
|
|
|
|
name: ::DiscourseSolved::ENABLE_ACCEPTED_ANSWERS_CUSTOM_FIELD,
|
|
|
|
value: "false",
|
|
|
|
)
|
|
|
|
category_custom_field.save
|
|
|
|
category
|
|
|
|
end
|
2024-03-28 16:46:22 -04:00
|
|
|
fab!(:tag)
|
2023-06-20 10:52:02 -04:00
|
|
|
fab!(:topic_unsolved) do
|
|
|
|
Fabricate(
|
|
|
|
:custom_topic,
|
|
|
|
user: user,
|
|
|
|
category: category_enabled,
|
|
|
|
custom_topic_name: ::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD,
|
|
|
|
)
|
|
|
|
end
|
2024-03-28 16:46:22 -04:00
|
|
|
fab!(:topic_unsolved_2) { Fabricate(:topic, user: user, tags: [tag]) }
|
2023-06-20 10:52:02 -04:00
|
|
|
fab!(:topic_solved) do
|
|
|
|
Fabricate(
|
|
|
|
:custom_topic,
|
|
|
|
user: user,
|
|
|
|
category: category_enabled,
|
|
|
|
custom_topic_name: ::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD,
|
|
|
|
)
|
|
|
|
end
|
|
|
|
fab!(:topic_disabled_1) do
|
|
|
|
Fabricate(
|
|
|
|
:custom_topic,
|
|
|
|
user: user,
|
|
|
|
category: category_disabled,
|
|
|
|
custom_topic_name: ::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD,
|
|
|
|
)
|
|
|
|
end
|
|
|
|
fab!(:topic_disabled_2) do
|
|
|
|
Fabricate(
|
|
|
|
:custom_topic,
|
|
|
|
user: user,
|
|
|
|
category: category_disabled,
|
|
|
|
custom_topic_name: "another_custom_field",
|
|
|
|
)
|
|
|
|
end
|
|
|
|
fab!(:post_unsolved) { Fabricate(:post, topic: topic_unsolved) }
|
2024-03-28 16:46:22 -04:00
|
|
|
fab!(:post_unsolved_2) { Fabricate(:post, topic: topic_unsolved_2) }
|
2023-06-20 10:52:02 -04:00
|
|
|
fab!(:post_solved) do
|
|
|
|
post = Fabricate(:post, topic: topic_solved)
|
|
|
|
DiscourseSolved.accept_answer!(post, Discourse.system_user)
|
|
|
|
post
|
|
|
|
end
|
|
|
|
fab!(:post_disabled_1) { Fabricate(:post, topic: topic_disabled_1) }
|
|
|
|
fab!(:post_disabled_2) { Fabricate(:post, topic: topic_disabled_2) }
|
|
|
|
|
|
|
|
before do
|
2024-03-28 16:46:22 -04:00
|
|
|
SiteSetting.enable_solved_tags = tag.name
|
2023-06-20 10:52:02 -04:00
|
|
|
SearchIndexer.enable
|
|
|
|
Jobs.run_immediately!
|
|
|
|
|
|
|
|
SearchIndexer.index(topic_unsolved, force: true)
|
2024-03-28 16:46:22 -04:00
|
|
|
SearchIndexer.index(topic_unsolved_2, force: true)
|
2023-06-20 10:52:02 -04:00
|
|
|
SearchIndexer.index(topic_solved, force: true)
|
|
|
|
SearchIndexer.index(topic_disabled_1, force: true)
|
|
|
|
SearchIndexer.index(topic_disabled_2, force: true)
|
|
|
|
end
|
|
|
|
|
|
|
|
after { SearchIndexer.disable }
|
|
|
|
|
|
|
|
describe "searches for unsolved topics" do
|
|
|
|
describe "when allow solved on all topics is disabled" do
|
|
|
|
before { SiteSetting.allow_solved_on_all_topics = false }
|
|
|
|
|
2024-03-28 16:46:22 -04:00
|
|
|
it "only returns unsolved posts from categories and tags where solving is enabled" do
|
2023-06-20 10:52:02 -04:00
|
|
|
result = Search.execute("status:unsolved")
|
2024-03-28 16:46:22 -04:00
|
|
|
expect(result.posts.pluck(:id)).to match_array([post_unsolved.id, post_unsolved_2.id])
|
|
|
|
end
|
|
|
|
|
|
|
|
it "returns the filtered results when combining search with a tag" do
|
|
|
|
result = Search.execute("status:unsolved tag:#{tag.name}")
|
|
|
|
expect(result.posts.pluck(:id)).to match_array([post_unsolved_2.id])
|
2023-06-20 10:52:02 -04:00
|
|
|
end
|
|
|
|
end
|
2024-03-28 16:46:22 -04:00
|
|
|
|
2023-06-20 10:52:02 -04:00
|
|
|
describe "when allow solved on all topics is enabled" do
|
|
|
|
before { SiteSetting.allow_solved_on_all_topics = true }
|
|
|
|
it "only returns posts where the post is not solved" do
|
|
|
|
result = Search.execute("status:unsolved")
|
|
|
|
expect(result.posts.pluck(:id)).to match_array(
|
2024-03-28 16:46:22 -04:00
|
|
|
[post_unsolved.id, post_unsolved_2.id, post_disabled_1.id, post_disabled_2.id],
|
2023-06-20 10:52:02 -04:00
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2023-05-02 21:56:10 -04:00
|
|
|
end
|
|
|
|
|
2022-12-23 15:36:08 -05:00
|
|
|
describe "auto bump" do
|
|
|
|
it "does not automatically bump solved topics" do
|
2019-08-06 06:39:09 -04:00
|
|
|
category = Fabricate(:category_with_definition)
|
2018-07-17 20:56:56 -04:00
|
|
|
|
|
|
|
post = create_post(category: category)
|
|
|
|
post2 = create_post(category: category)
|
|
|
|
|
|
|
|
DiscourseSolved.accept_answer!(post, Discourse.system_user)
|
|
|
|
|
|
|
|
category.num_auto_bump_daily = 2
|
|
|
|
category.save!
|
|
|
|
|
|
|
|
freeze_time 1.month.from_now
|
|
|
|
|
|
|
|
expect(category.auto_bump_topic!).to eq(true)
|
|
|
|
|
|
|
|
freeze_time 13.hours.from_now
|
|
|
|
|
|
|
|
expect(category.auto_bump_topic!).to eq(false)
|
|
|
|
|
|
|
|
expect(post.topic.reload.posts_count).to eq(1)
|
|
|
|
expect(post2.topic.reload.posts_count).to eq(2)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-12-23 15:36:08 -05:00
|
|
|
describe "accepting a post as the answer" do
|
2017-05-22 05:09:40 -04:00
|
|
|
before do
|
|
|
|
sign_in(user)
|
|
|
|
SiteSetting.solved_topics_auto_close_hours = 2
|
|
|
|
end
|
|
|
|
|
2022-12-23 15:36:08 -05:00
|
|
|
it "can mark a post as the accepted answer correctly" do
|
2018-08-31 00:03:42 -04:00
|
|
|
freeze_time
|
|
|
|
|
2017-08-31 22:42:31 -04:00
|
|
|
post "/solution/accept.json", params: { id: p1.id }
|
2017-05-22 05:09:40 -04:00
|
|
|
|
2018-08-31 00:03:42 -04:00
|
|
|
expect(response.status).to eq(200)
|
2017-05-22 05:09:40 -04:00
|
|
|
expect(p1.reload.custom_fields["is_accepted_answer"]).to eq("true")
|
|
|
|
|
2022-02-23 04:57:32 -05:00
|
|
|
topic.reload
|
|
|
|
|
2022-12-23 15:36:08 -05:00
|
|
|
expect(topic.public_topic_timer.status_type).to eq(TopicTimer.types[:silent_close])
|
2018-08-31 00:03:42 -04:00
|
|
|
|
2023-10-13 13:06:03 -04:00
|
|
|
expect(topic.custom_fields["solved_auto_close_topic_timer_id"].to_i).to eq(
|
2022-12-23 15:36:08 -05:00
|
|
|
topic.public_topic_timer.id,
|
|
|
|
)
|
2017-05-22 05:09:40 -04:00
|
|
|
|
2022-12-23 15:36:08 -05:00
|
|
|
expect(topic.public_topic_timer.execute_at).to eq_time(Time.zone.now + 2.hours)
|
2017-05-22 05:09:40 -04:00
|
|
|
|
|
|
|
expect(topic.public_topic_timer.based_on_last_post).to eq(true)
|
|
|
|
end
|
2018-08-31 00:03:42 -04:00
|
|
|
|
2023-04-19 10:14:33 -04:00
|
|
|
it "gives priority to category's solved_topics_auto_close_hours setting" do
|
|
|
|
freeze_time
|
|
|
|
custom_auto_close_category = Fabricate(:category)
|
|
|
|
topic_2 = Fabricate(:topic, category: custom_auto_close_category)
|
|
|
|
post_2 = Fabricate(:post, topic: topic_2)
|
|
|
|
custom_auto_close_category.custom_fields["solved_topics_auto_close_hours"] = 4
|
|
|
|
custom_auto_close_category.save_custom_fields
|
|
|
|
|
|
|
|
post "/solution/accept.json", params: { id: post_2.id }
|
|
|
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
expect(post_2.reload.custom_fields["is_accepted_answer"]).to eq("true")
|
|
|
|
|
|
|
|
topic_2.reload
|
|
|
|
|
|
|
|
expect(topic_2.public_topic_timer.status_type).to eq(TopicTimer.types[:silent_close])
|
|
|
|
|
2023-10-13 13:06:03 -04:00
|
|
|
expect(topic_2.custom_fields["solved_auto_close_topic_timer_id"].to_i).to eq(
|
|
|
|
topic_2.public_topic_timer.id,
|
|
|
|
)
|
2023-04-19 10:14:33 -04:00
|
|
|
|
|
|
|
expect(topic_2.public_topic_timer.execute_at).to eq_time(Time.zone.now + 4.hours)
|
|
|
|
|
|
|
|
expect(topic_2.public_topic_timer.based_on_last_post).to eq(true)
|
|
|
|
end
|
|
|
|
|
2022-12-23 15:36:08 -05:00
|
|
|
it "sends notifications to correct users" do
|
2020-10-30 12:31:27 -04:00
|
|
|
SiteSetting.notify_on_staff_accept_solved = true
|
|
|
|
user = Fabricate(:user)
|
|
|
|
topic = Fabricate(:topic, user: user)
|
|
|
|
post = Fabricate(:post, post_number: 2, topic: topic)
|
|
|
|
|
|
|
|
op = topic.user
|
|
|
|
user = post.user
|
|
|
|
|
2022-12-23 15:36:08 -05:00
|
|
|
expect { DiscourseSolved.accept_answer!(post, Discourse.system_user) }.to change {
|
|
|
|
user.notifications.count
|
|
|
|
}.by(1) & change { op.notifications.count }.by(1)
|
2020-10-30 12:31:27 -04:00
|
|
|
|
|
|
|
notification = user.notifications.last
|
|
|
|
expect(notification.notification_type).to eq(Notification.types[:custom])
|
|
|
|
expect(notification.topic_id).to eq(post.topic_id)
|
|
|
|
expect(notification.post_number).to eq(post.post_number)
|
|
|
|
|
|
|
|
notification = op.notifications.last
|
|
|
|
expect(notification.notification_type).to eq(Notification.types[:custom])
|
|
|
|
expect(notification.topic_id).to eq(post.topic_id)
|
|
|
|
expect(notification.post_number).to eq(post.post_number)
|
|
|
|
end
|
|
|
|
|
2022-12-23 15:36:08 -05:00
|
|
|
it "does not set a timer when the topic is closed" do
|
2017-07-18 15:24:09 -04:00
|
|
|
topic.update!(closed: true)
|
2017-08-31 22:42:31 -04:00
|
|
|
post "/solution/accept.json", params: { id: p1.id }
|
2018-08-31 00:03:42 -04:00
|
|
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
2017-07-18 15:24:09 -04:00
|
|
|
p1.reload
|
|
|
|
topic.reload
|
2017-06-12 16:29:53 -04:00
|
|
|
|
2017-07-18 15:24:09 -04:00
|
|
|
expect(p1.custom_fields["is_accepted_answer"]).to eq("true")
|
|
|
|
expect(topic.public_topic_timer).to eq(nil)
|
|
|
|
expect(topic.closed).to eq(true)
|
2017-06-12 16:29:53 -04:00
|
|
|
end
|
2019-03-18 11:27:29 -04:00
|
|
|
|
2022-12-23 15:36:08 -05:00
|
|
|
it "works with staff and trashed topics" do
|
2019-03-18 11:27:29 -04:00
|
|
|
topic.trash!(Discourse.system_user)
|
|
|
|
|
|
|
|
post "/solution/accept.json", params: { id: p1.id }
|
|
|
|
expect(response.status).to eq(403)
|
|
|
|
|
|
|
|
sign_in(Fabricate(:admin))
|
|
|
|
post "/solution/accept.json", params: { id: p1.id }
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
|
|
|
p1.reload
|
|
|
|
expect(p1.custom_fields["is_accepted_answer"]).to eq("true")
|
|
|
|
end
|
2020-02-23 03:16:31 -05:00
|
|
|
|
2023-10-13 13:06:03 -04:00
|
|
|
it "removes the solution when the post is deleted" do
|
|
|
|
reply = Fabricate(:post, post_number: 2, topic: topic)
|
|
|
|
|
|
|
|
post "/solution/accept.json", params: { id: reply.id }
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
|
|
|
reply.reload
|
|
|
|
expect(reply.custom_fields["is_accepted_answer"]).to eq("true")
|
|
|
|
expect(reply.topic.custom_fields["accepted_answer_post_id"].to_i).to eq(reply.id)
|
|
|
|
|
|
|
|
PostDestroyer.new(Discourse.system_user, reply).destroy
|
|
|
|
|
|
|
|
reply.reload
|
|
|
|
expect(reply.custom_fields["is_accepted_answer"]).to eq(nil)
|
|
|
|
expect(reply.topic.custom_fields["accepted_answer_post_id"]).to eq(nil)
|
|
|
|
end
|
|
|
|
|
2022-12-23 15:36:08 -05:00
|
|
|
it "does not allow you to accept a whisper" do
|
2020-02-23 03:16:31 -05:00
|
|
|
whisper = Fabricate(:post, topic: topic, post_type: Post.types[:whisper])
|
|
|
|
sign_in(Fabricate(:admin))
|
|
|
|
|
|
|
|
post "/solution/accept.json", params: { id: whisper.id }
|
|
|
|
expect(response.status).to eq(403)
|
|
|
|
end
|
FEATURE: Publish WebHook event when solving/unsolving (#85)
* FEATURE: Publish WebHook event when solving/unsolving
This feature will publish a post edit webhook event whenever a solution
is accepted or unaccepted.
I went ahead and used the existing post-edit webhook because all the
post custom fields for the solved plugin are already included in the
post-edit serializer.
* Create Solved Event Webhook
This commit adds a solved event webhook that will only trigger when an
answer has been marked as accepted or unaccepted.
It uses 100 as the webhook ID. This way any new webhooks in core can
keep using lower numbers like 11, 12, 13, but plugins can use 101, 102,
etc.
* Removed functionality that was added to core
This [PR][1] to discourse core adds what what removed in this commit.
It is better to have this logic in core so that it is discoverable and
future webhooks won't end up accidentally using the same ID.
[1]: https://github.com/discourse/discourse/pull/9110
* UX: Add "solved" status filter in advanced search page.
And rename `in:solved` to `status:solved`.
* FEATURE: Publish WebHook event when solving/unsolving
This feature will publish a post edit webhook event whenever a solution
is accepted or unaccepted.
I went ahead and used the existing post-edit webhook because all the
post custom fields for the solved plugin are already included in the
post-edit serializer.
* Create Solved Event Webhook
This commit adds a solved event webhook that will only trigger when an
answer has been marked as accepted or unaccepted.
It uses 100 as the webhook ID. This way any new webhooks in core can
keep using lower numbers like 11, 12, 13, but plugins can use 101, 102,
etc.
* Removed functionality that was added to core
This [PR][1] to discourse core adds what what removed in this commit.
It is better to have this logic in core so that it is discoverable and
future webhooks won't end up accidentally using the same ID.
[1]: https://github.com/discourse/discourse/pull/9110
Co-authored-by: Vinoth Kannan <vinothkannan@vinkas.com>
2020-03-06 13:28:29 -05:00
|
|
|
|
2022-12-23 15:36:08 -05:00
|
|
|
it "triggers a webhook" do
|
FEATURE: Publish WebHook event when solving/unsolving (#85)
* FEATURE: Publish WebHook event when solving/unsolving
This feature will publish a post edit webhook event whenever a solution
is accepted or unaccepted.
I went ahead and used the existing post-edit webhook because all the
post custom fields for the solved plugin are already included in the
post-edit serializer.
* Create Solved Event Webhook
This commit adds a solved event webhook that will only trigger when an
answer has been marked as accepted or unaccepted.
It uses 100 as the webhook ID. This way any new webhooks in core can
keep using lower numbers like 11, 12, 13, but plugins can use 101, 102,
etc.
* Removed functionality that was added to core
This [PR][1] to discourse core adds what what removed in this commit.
It is better to have this logic in core so that it is discoverable and
future webhooks won't end up accidentally using the same ID.
[1]: https://github.com/discourse/discourse/pull/9110
* UX: Add "solved" status filter in advanced search page.
And rename `in:solved` to `status:solved`.
* FEATURE: Publish WebHook event when solving/unsolving
This feature will publish a post edit webhook event whenever a solution
is accepted or unaccepted.
I went ahead and used the existing post-edit webhook because all the
post custom fields for the solved plugin are already included in the
post-edit serializer.
* Create Solved Event Webhook
This commit adds a solved event webhook that will only trigger when an
answer has been marked as accepted or unaccepted.
It uses 100 as the webhook ID. This way any new webhooks in core can
keep using lower numbers like 11, 12, 13, but plugins can use 101, 102,
etc.
* Removed functionality that was added to core
This [PR][1] to discourse core adds what what removed in this commit.
It is better to have this logic in core so that it is discoverable and
future webhooks won't end up accidentally using the same ID.
[1]: https://github.com/discourse/discourse/pull/9110
Co-authored-by: Vinoth Kannan <vinothkannan@vinkas.com>
2020-03-06 13:28:29 -05:00
|
|
|
Fabricate(:solved_web_hook)
|
|
|
|
post "/solution/accept.json", params: { id: p1.id }
|
|
|
|
|
|
|
|
job_args = Jobs::EmitWebHookEvent.jobs[0]["args"].first
|
|
|
|
|
|
|
|
expect(job_args["event_name"]).to eq("accepted_solution")
|
|
|
|
payload = JSON.parse(job_args["payload"])
|
|
|
|
expect(payload["id"]).to eq(p1.id)
|
|
|
|
end
|
2017-05-22 05:09:40 -04:00
|
|
|
end
|
2018-08-31 00:03:42 -04:00
|
|
|
|
2022-12-23 15:36:08 -05:00
|
|
|
describe "#unaccept" do
|
|
|
|
before { sign_in(user) }
|
2018-08-31 00:03:42 -04:00
|
|
|
|
2022-12-23 15:36:08 -05:00
|
|
|
describe "when solved_topics_auto_close_hours is enabled" do
|
2018-08-31 00:03:42 -04:00
|
|
|
before do
|
|
|
|
SiteSetting.solved_topics_auto_close_hours = 2
|
|
|
|
DiscourseSolved.accept_answer!(p1, user)
|
|
|
|
end
|
|
|
|
|
2022-12-23 15:36:08 -05:00
|
|
|
it "should unmark the post as solved" do
|
|
|
|
expect do post "/solution/unaccept.json", params: { id: p1.id } end.to change {
|
|
|
|
topic.reload.public_topic_timer
|
|
|
|
}.to(nil)
|
2018-08-31 00:03:42 -04:00
|
|
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
p1.reload
|
|
|
|
|
|
|
|
expect(p1.custom_fields["is_accepted_answer"]).to eq(nil)
|
|
|
|
expect(p1.topic.custom_fields["accepted_answer_post_id"]).to eq(nil)
|
|
|
|
end
|
FEATURE: Publish WebHook event when solving/unsolving (#85)
* FEATURE: Publish WebHook event when solving/unsolving
This feature will publish a post edit webhook event whenever a solution
is accepted or unaccepted.
I went ahead and used the existing post-edit webhook because all the
post custom fields for the solved plugin are already included in the
post-edit serializer.
* Create Solved Event Webhook
This commit adds a solved event webhook that will only trigger when an
answer has been marked as accepted or unaccepted.
It uses 100 as the webhook ID. This way any new webhooks in core can
keep using lower numbers like 11, 12, 13, but plugins can use 101, 102,
etc.
* Removed functionality that was added to core
This [PR][1] to discourse core adds what what removed in this commit.
It is better to have this logic in core so that it is discoverable and
future webhooks won't end up accidentally using the same ID.
[1]: https://github.com/discourse/discourse/pull/9110
* UX: Add "solved" status filter in advanced search page.
And rename `in:solved` to `status:solved`.
* FEATURE: Publish WebHook event when solving/unsolving
This feature will publish a post edit webhook event whenever a solution
is accepted or unaccepted.
I went ahead and used the existing post-edit webhook because all the
post custom fields for the solved plugin are already included in the
post-edit serializer.
* Create Solved Event Webhook
This commit adds a solved event webhook that will only trigger when an
answer has been marked as accepted or unaccepted.
It uses 100 as the webhook ID. This way any new webhooks in core can
keep using lower numbers like 11, 12, 13, but plugins can use 101, 102,
etc.
* Removed functionality that was added to core
This [PR][1] to discourse core adds what what removed in this commit.
It is better to have this logic in core so that it is discoverable and
future webhooks won't end up accidentally using the same ID.
[1]: https://github.com/discourse/discourse/pull/9110
Co-authored-by: Vinoth Kannan <vinothkannan@vinkas.com>
2020-03-06 13:28:29 -05:00
|
|
|
end
|
|
|
|
|
2022-12-23 15:36:08 -05:00
|
|
|
it "triggers a webhook" do
|
FEATURE: Publish WebHook event when solving/unsolving (#85)
* FEATURE: Publish WebHook event when solving/unsolving
This feature will publish a post edit webhook event whenever a solution
is accepted or unaccepted.
I went ahead and used the existing post-edit webhook because all the
post custom fields for the solved plugin are already included in the
post-edit serializer.
* Create Solved Event Webhook
This commit adds a solved event webhook that will only trigger when an
answer has been marked as accepted or unaccepted.
It uses 100 as the webhook ID. This way any new webhooks in core can
keep using lower numbers like 11, 12, 13, but plugins can use 101, 102,
etc.
* Removed functionality that was added to core
This [PR][1] to discourse core adds what what removed in this commit.
It is better to have this logic in core so that it is discoverable and
future webhooks won't end up accidentally using the same ID.
[1]: https://github.com/discourse/discourse/pull/9110
* UX: Add "solved" status filter in advanced search page.
And rename `in:solved` to `status:solved`.
* FEATURE: Publish WebHook event when solving/unsolving
This feature will publish a post edit webhook event whenever a solution
is accepted or unaccepted.
I went ahead and used the existing post-edit webhook because all the
post custom fields for the solved plugin are already included in the
post-edit serializer.
* Create Solved Event Webhook
This commit adds a solved event webhook that will only trigger when an
answer has been marked as accepted or unaccepted.
It uses 100 as the webhook ID. This way any new webhooks in core can
keep using lower numbers like 11, 12, 13, but plugins can use 101, 102,
etc.
* Removed functionality that was added to core
This [PR][1] to discourse core adds what what removed in this commit.
It is better to have this logic in core so that it is discoverable and
future webhooks won't end up accidentally using the same ID.
[1]: https://github.com/discourse/discourse/pull/9110
Co-authored-by: Vinoth Kannan <vinothkannan@vinkas.com>
2020-03-06 13:28:29 -05:00
|
|
|
Fabricate(:solved_web_hook)
|
|
|
|
post "/solution/unaccept.json", params: { id: p1.id }
|
|
|
|
|
|
|
|
job_args = Jobs::EmitWebHookEvent.jobs[0]["args"].first
|
|
|
|
|
|
|
|
expect(job_args["event_name"]).to eq("unaccepted_solution")
|
|
|
|
payload = JSON.parse(job_args["payload"])
|
|
|
|
expect(payload["id"]).to eq(p1.id)
|
2018-08-31 00:03:42 -04:00
|
|
|
end
|
|
|
|
end
|
2020-07-27 17:42:42 -04:00
|
|
|
|
2022-12-23 15:36:08 -05:00
|
|
|
context "with group moderators" do
|
2024-03-05 06:14:32 -05:00
|
|
|
fab!(:group_user)
|
2024-09-03 21:37:17 -04:00
|
|
|
let!(:category_moderation_group) do
|
|
|
|
Fabricate(:category_moderation_group, category: p1.topic.category, group: group_user.group)
|
|
|
|
end
|
2020-07-27 17:42:42 -04:00
|
|
|
let(:user_gm) { group_user.user }
|
|
|
|
|
|
|
|
before do
|
|
|
|
SiteSetting.enable_category_group_moderation = true
|
|
|
|
sign_in(user_gm)
|
|
|
|
end
|
|
|
|
|
2022-12-23 15:36:08 -05:00
|
|
|
it "can accept a solution" do
|
2020-07-27 17:42:42 -04:00
|
|
|
post "/solution/accept.json", params: { id: p1.id }
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
end
|
|
|
|
end
|
2024-04-26 12:21:09 -04:00
|
|
|
|
|
|
|
context "with discourse-assign installed", if: defined?(DiscourseAssign) do
|
|
|
|
let(:admin) { Fabricate(:admin) }
|
|
|
|
fab!(:group)
|
|
|
|
before do
|
|
|
|
SiteSetting.solved_enabled = true
|
|
|
|
SiteSetting.assign_enabled = true
|
|
|
|
SiteSetting.enable_assign_status = true
|
|
|
|
SiteSetting.assign_allowed_on_groups = "#{group.id}"
|
|
|
|
SiteSetting.assigns_public = true
|
|
|
|
SiteSetting.assignment_status_on_solve = "Done"
|
2024-05-08 14:17:45 -04:00
|
|
|
SiteSetting.assignment_status_on_unsolve = "New"
|
2024-04-26 12:21:09 -04:00
|
|
|
SiteSetting.ignore_solved_topics_in_assigned_reminder = false
|
|
|
|
group.add(p1.acting_user)
|
|
|
|
group.add(user)
|
|
|
|
|
|
|
|
sign_in(user)
|
|
|
|
end
|
2024-06-17 06:41:13 -04:00
|
|
|
|
2024-04-26 12:21:09 -04:00
|
|
|
describe "updating assignment status on solve when assignment_status_on_solve is set" do
|
|
|
|
it "update all assignments to this status when a post is accepted" do
|
|
|
|
assigner = Assigner.new(p1.topic, user)
|
|
|
|
result = assigner.assign(user)
|
|
|
|
expect(result[:success]).to eq(true)
|
|
|
|
|
|
|
|
expect(p1.topic.assignment.status).to eq("New")
|
|
|
|
DiscourseSolved.accept_answer!(p1, user)
|
|
|
|
|
|
|
|
expect(p1.reload.custom_fields["is_accepted_answer"]).to eq("true")
|
|
|
|
expect(p1.topic.assignment.reload.status).to eq("Done")
|
|
|
|
end
|
|
|
|
|
2024-05-08 14:17:45 -04:00
|
|
|
it "update all assignments to this status when a post is unaccepted" do
|
|
|
|
assigner = Assigner.new(p1.topic, user)
|
|
|
|
result = assigner.assign(user)
|
|
|
|
expect(result[:success]).to eq(true)
|
|
|
|
|
|
|
|
DiscourseSolved.accept_answer!(p1, user)
|
|
|
|
|
|
|
|
expect(p1.reload.topic.assignment.reload.status).to eq("Done")
|
|
|
|
|
|
|
|
DiscourseSolved.unaccept_answer!(p1)
|
|
|
|
|
|
|
|
expect(p1.reload.custom_fields["is_accepted_answer"]).to eq(nil)
|
|
|
|
expect(p1.reload.topic.assignment.reload.status).to eq("New")
|
|
|
|
end
|
|
|
|
|
|
|
|
it "does not update the assignee when a post is accepted" do
|
|
|
|
user_1 = Fabricate(:user)
|
|
|
|
user_2 = Fabricate(:user)
|
|
|
|
user_3 = Fabricate(:user)
|
|
|
|
group.add(user_1)
|
|
|
|
group.add(user_2)
|
|
|
|
group.add(user_3)
|
|
|
|
|
|
|
|
topic_question = Fabricate(:topic, user: user_1)
|
|
|
|
|
2024-06-17 06:41:13 -04:00
|
|
|
Fabricate(:post, topic: topic_question, user: user_1)
|
|
|
|
Fabricate(:post, topic: topic_question, user: user_2)
|
|
|
|
|
|
|
|
result = Assigner.new(topic_question, user_2).assign(user_2)
|
2024-05-08 14:17:45 -04:00
|
|
|
expect(result[:success]).to eq(true)
|
|
|
|
|
|
|
|
post_response = Fabricate(:post, topic: topic_question, user: user_3)
|
|
|
|
Assigner.new(post_response, user_3).assign(user_3)
|
|
|
|
|
|
|
|
DiscourseSolved.accept_answer!(post_response, user_1)
|
|
|
|
|
|
|
|
expect(topic_question.assignment.assigned_to_id).to eq(user_2.id)
|
|
|
|
expect(post_response.assignment.assigned_to_id).to eq(user_3.id)
|
|
|
|
DiscourseSolved.unaccept_answer!(post_response)
|
|
|
|
|
|
|
|
expect(topic_question.assignment.assigned_to_id).to eq(user_2.id)
|
|
|
|
expect(post_response.assignment.assigned_to_id).to eq(user_3.id)
|
|
|
|
end
|
|
|
|
|
2024-06-17 06:41:13 -04:00
|
|
|
describe "assigned topic reminder" do
|
|
|
|
it "excludes solved topics when ignore_solved_topics_in_assigned_reminder is false" do
|
|
|
|
other_topic = Fabricate(:topic, title: "Topic that should be there")
|
|
|
|
post = Fabricate(:post, topic: other_topic, user: user)
|
2024-04-26 12:21:09 -04:00
|
|
|
|
2024-06-17 06:41:13 -04:00
|
|
|
other_topic2 = Fabricate(:topic, title: "Topic that should be there2")
|
|
|
|
post2 = Fabricate(:post, topic: other_topic2, user: user)
|
2024-04-26 12:21:09 -04:00
|
|
|
|
2024-06-17 06:41:13 -04:00
|
|
|
Assigner.new(post.topic, user).assign(user)
|
|
|
|
Assigner.new(post2.topic, user).assign(user)
|
2024-04-26 12:21:09 -04:00
|
|
|
|
2024-06-17 06:41:13 -04:00
|
|
|
reminder = PendingAssignsReminder.new
|
|
|
|
topics = reminder.send(:assigned_topics, user, order: :asc)
|
|
|
|
expect(topics.to_a.length).to eq(2)
|
2024-04-26 12:21:09 -04:00
|
|
|
|
2024-06-17 06:41:13 -04:00
|
|
|
DiscourseSolved.accept_answer!(post2, Discourse.system_user)
|
|
|
|
topics = reminder.send(:assigned_topics, user, order: :asc)
|
|
|
|
expect(topics.to_a.length).to eq(2)
|
|
|
|
expect(topics).to include(other_topic2)
|
2024-04-26 12:21:09 -04:00
|
|
|
|
2024-06-17 06:41:13 -04:00
|
|
|
SiteSetting.ignore_solved_topics_in_assigned_reminder = true
|
|
|
|
topics = reminder.send(:assigned_topics, user, order: :asc)
|
|
|
|
expect(topics.to_a.length).to eq(1)
|
|
|
|
expect(topics).not_to include(other_topic2)
|
|
|
|
expect(topics).to include(other_topic)
|
|
|
|
end
|
2024-04-26 12:21:09 -04:00
|
|
|
end
|
2024-10-30 10:16:46 -04:00
|
|
|
|
|
|
|
describe "assigned count for user" do
|
|
|
|
it "does not count solved topics using assignment_status_on_solve status" do
|
|
|
|
SiteSetting.ignore_solved_topics_in_assigned_reminder = true
|
|
|
|
|
|
|
|
other_topic = Fabricate(:topic, title: "Topic that should be there")
|
|
|
|
post = Fabricate(:post, topic: other_topic, user: user)
|
|
|
|
|
|
|
|
other_topic2 = Fabricate(:topic, title: "Topic that should be there2")
|
|
|
|
post2 = Fabricate(:post, topic: other_topic2, user: user)
|
|
|
|
|
|
|
|
Assigner.new(post.topic, user).assign(user)
|
|
|
|
Assigner.new(post2.topic, user).assign(user)
|
|
|
|
|
|
|
|
reminder = PendingAssignsReminder.new
|
|
|
|
expect(reminder.send(:assigned_count_for, user)).to eq(2)
|
|
|
|
|
|
|
|
DiscourseSolved.accept_answer!(post2, Discourse.system_user)
|
|
|
|
expect(reminder.send(:assigned_count_for, user)).to eq(1)
|
|
|
|
end
|
|
|
|
end
|
2024-04-26 12:21:09 -04:00
|
|
|
end
|
|
|
|
end
|
2024-06-17 06:41:13 -04:00
|
|
|
|
|
|
|
describe "#unaccept_answer!" do
|
|
|
|
it "works even when the topic has been deleted" do
|
|
|
|
user = Fabricate(:user, trust_level: 1)
|
|
|
|
topic = Fabricate(:topic, user:)
|
|
|
|
reply = Fabricate(:post, topic:, user:, post_number: 2)
|
|
|
|
|
|
|
|
DiscourseSolved.accept_answer!(reply, user)
|
|
|
|
|
|
|
|
topic.trash!(Discourse.system_user)
|
|
|
|
reply.reload
|
|
|
|
|
|
|
|
expect(reply.topic).to eq(nil)
|
|
|
|
|
|
|
|
expect { DiscourseSolved.unaccept_answer!(reply) }.not_to raise_error
|
|
|
|
end
|
|
|
|
end
|
2017-05-22 05:09:40 -04:00
|
|
|
end
|