DEV: Introduce syntax_tree for ruby formatting (#217)
This commit is contained in:
parent
d07ffb6f7a
commit
14e0800a29
|
@ -55,3 +55,12 @@ jobs:
|
|||
- name: Rubocop
|
||||
if: ${{ !cancelled() }}
|
||||
run: bundle exec rubocop .
|
||||
|
||||
- name: Syntax Tree
|
||||
if: ${{ !cancelled() }}
|
||||
run: |
|
||||
if test -f .streerc; then
|
||||
bundle exec stree check Gemfile $(git ls-files '*.rb') $(git ls-files '*.rake')
|
||||
else
|
||||
echo "Stree config not detected for this repository. Skipping."
|
||||
fi
|
||||
|
|
|
@ -80,7 +80,7 @@ jobs:
|
|||
|
||||
- name: Get yarn cache directory
|
||||
id: yarn-cache-dir
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Yarn cache
|
||||
uses: actions/cache@v3
|
||||
|
@ -130,7 +130,7 @@ jobs:
|
|||
shell: bash
|
||||
run: |
|
||||
if [ 0 -lt $(find plugins/${{ github.event.repository.name }}/spec -type f -name "*.rb" 2> /dev/null | wc -l) ]; then
|
||||
echo "::set-output name=files_exist::true"
|
||||
echo "files_exist=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Plugin RSpec
|
||||
|
@ -142,7 +142,7 @@ jobs:
|
|||
shell: bash
|
||||
run: |
|
||||
if [ 0 -lt $(find plugins/${{ github.event.repository.name }}/test/javascripts -type f \( -name "*.js" -or -name "*.es6" \) 2> /dev/null | wc -l) ]; then
|
||||
echo "::set-output name=files_exist::true"
|
||||
echo "files_exist=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Plugin QUnit
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
inherit_gem:
|
||||
rubocop-discourse: default.yml
|
||||
rubocop-discourse: stree-compat.yml
|
||||
|
|
5
Gemfile
5
Gemfile
|
@ -1,7 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
source 'https://rubygems.org'
|
||||
source "https://rubygems.org"
|
||||
|
||||
group :development do
|
||||
gem 'rubocop-discourse'
|
||||
gem "rubocop-discourse"
|
||||
gem "syntax_tree"
|
||||
end
|
||||
|
|
|
@ -6,6 +6,7 @@ GEM
|
|||
parallel (1.22.1)
|
||||
parser (3.1.2.1)
|
||||
ast (~> 2.4.1)
|
||||
prettier_print (1.1.0)
|
||||
rainbow (3.1.1)
|
||||
regexp_parser (2.6.0)
|
||||
rexml (3.2.5)
|
||||
|
@ -27,6 +28,8 @@ GEM
|
|||
rubocop-rspec (2.13.2)
|
||||
rubocop (~> 1.33)
|
||||
ruby-progressbar (1.11.0)
|
||||
syntax_tree (5.0.1)
|
||||
prettier_print (>= 1.1.0)
|
||||
unicode-display_width (2.3.0)
|
||||
|
||||
PLATFORMS
|
||||
|
@ -34,6 +37,7 @@ PLATFORMS
|
|||
|
||||
DEPENDENCIES
|
||||
rubocop-discourse
|
||||
syntax_tree
|
||||
|
||||
BUNDLED WITH
|
||||
2.1.4
|
||||
|
|
|
@ -4,11 +4,9 @@ class FirstAcceptedPostSolutionValidator
|
|||
def self.check(post, trust_level:)
|
||||
return false if post.archetype != Archetype.default
|
||||
return false if !post&.user&.human?
|
||||
return true if trust_level == 'any'
|
||||
return true if trust_level == "any"
|
||||
|
||||
if TrustLevel.compare(post&.user&.trust_level, trust_level.to_i)
|
||||
return false
|
||||
end
|
||||
return false if TrustLevel.compare(post&.user&.trust_level, trust_level.to_i)
|
||||
|
||||
if !UserAction.where(user_id: post&.user_id, action_type: UserAction::SOLVED).exists?
|
||||
return true
|
||||
|
|
|
@ -12,9 +12,9 @@ class EnsuresUniqueAcceptedAnswerPostId < ActiveRecord::Migration[5.2]
|
|||
SQL
|
||||
|
||||
add_index :topic_custom_fields,
|
||||
:topic_id,
|
||||
name: :idx_topic_custom_fields_accepted_answer,
|
||||
unique: true,
|
||||
where: "name = 'accepted_answer_post_id'"
|
||||
:topic_id,
|
||||
name: :idx_topic_custom_fields_accepted_answer,
|
||||
unique: true,
|
||||
where: "name = 'accepted_answer_post_id'"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,7 +18,7 @@ class RenameBadges < ActiveRecord::Migration[6.1]
|
|||
"sv" => "Kundtjänst",
|
||||
"tr_TR" => "Yardım masası",
|
||||
"zh_CN" => "帮助台",
|
||||
"zh_TW" => "服務台"
|
||||
"zh_TW" => "服務台",
|
||||
}
|
||||
|
||||
TECH_SUPPORT_TRANSLATIONS = {
|
||||
|
@ -43,11 +43,12 @@ class RenameBadges < ActiveRecord::Migration[6.1]
|
|||
"sv" => "Teknisk support",
|
||||
"tr_TR" => "Teknik Destek",
|
||||
"zh_CN" => "技术支持",
|
||||
"zh_TW" => "技術支援"
|
||||
"zh_TW" => "技術支援",
|
||||
}
|
||||
|
||||
def up
|
||||
default_locale = DB.query_single("SELECT value FROM site_settings WHERE name = 'default_locale'").first || "en"
|
||||
default_locale =
|
||||
DB.query_single("SELECT value FROM site_settings WHERE name = 'default_locale'").first || "en"
|
||||
|
||||
sql = <<~SQL
|
||||
UPDATE badges
|
||||
|
|
417
plugin.rb
417
plugin.rb
|
@ -17,23 +17,22 @@ end
|
|||
|
||||
PLUGIN_NAME = "discourse_solved".freeze
|
||||
|
||||
register_asset 'stylesheets/solutions.scss'
|
||||
register_asset 'stylesheets/mobile/solutions.scss', :mobile
|
||||
register_asset "stylesheets/solutions.scss"
|
||||
register_asset "stylesheets/mobile/solutions.scss", :mobile
|
||||
|
||||
after_initialize do
|
||||
|
||||
SeedFu.fixture_paths << Rails.root.join("plugins", "discourse-solved", "db", "fixtures").to_s
|
||||
|
||||
[
|
||||
'../app/lib/first_accepted_post_solution_validator.rb',
|
||||
'../app/serializers/concerns/topic_answer_mixin.rb'
|
||||
%w[
|
||||
../app/lib/first_accepted_post_solution_validator.rb
|
||||
../app/serializers/concerns/topic_answer_mixin.rb
|
||||
].each { |path| load File.expand_path(path, __FILE__) }
|
||||
|
||||
skip_db = defined?(GlobalSetting.skip_db?) && GlobalSetting.skip_db?
|
||||
|
||||
# we got to do a one time upgrade
|
||||
if !skip_db && defined?(UserAction::SOLVED)
|
||||
unless Discourse.redis.get('solved_already_upgraded')
|
||||
unless Discourse.redis.get("solved_already_upgraded")
|
||||
unless UserAction.where(action_type: UserAction::SOLVED).exists?
|
||||
Rails.logger.info("Upgrading storage for solved")
|
||||
sql = <<SQL
|
||||
|
@ -89,10 +88,7 @@ SQL
|
|||
p2.save!
|
||||
|
||||
if defined?(UserAction::SOLVED)
|
||||
UserAction.where(
|
||||
action_type: UserAction::SOLVED,
|
||||
target_post_id: p2.id
|
||||
).destroy_all
|
||||
UserAction.where(action_type: UserAction::SOLVED, target_post_id: p2.id).destroy_all
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -106,14 +102,14 @@ SQL
|
|||
user_id: post.user_id,
|
||||
acting_user_id: acting_user.id,
|
||||
target_post_id: post.id,
|
||||
target_topic_id: post.topic_id
|
||||
target_topic_id: post.topic_id,
|
||||
)
|
||||
end
|
||||
|
||||
notification_data = {
|
||||
message: 'solved.accepted_notification',
|
||||
message: "solved.accepted_notification",
|
||||
display_username: acting_user.username,
|
||||
topic_title: topic.title
|
||||
topic_title: topic.title,
|
||||
}.to_json
|
||||
|
||||
unless acting_user.id == post.user_id
|
||||
|
@ -122,7 +118,7 @@ SQL
|
|||
user_id: post.user_id,
|
||||
topic_id: post.topic_id,
|
||||
post_number: post.post_number,
|
||||
data: notification_data
|
||||
data: notification_data,
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -132,23 +128,22 @@ SQL
|
|||
user_id: topic.user_id,
|
||||
topic_id: post.topic_id,
|
||||
post_number: post.post_number,
|
||||
data: notification_data
|
||||
data: notification_data,
|
||||
)
|
||||
end
|
||||
|
||||
auto_close_hours = SiteSetting.solved_topics_auto_close_hours
|
||||
|
||||
if (auto_close_hours > 0) && !topic.closed
|
||||
topic_timer = topic.set_or_create_timer(
|
||||
TopicTimer.types[:silent_close],
|
||||
nil,
|
||||
based_on_last_post: true,
|
||||
duration_minutes: auto_close_hours * 60
|
||||
)
|
||||
topic_timer =
|
||||
topic.set_or_create_timer(
|
||||
TopicTimer.types[:silent_close],
|
||||
nil,
|
||||
based_on_last_post: true,
|
||||
duration_minutes: auto_close_hours * 60,
|
||||
)
|
||||
|
||||
topic.custom_fields[
|
||||
AUTO_CLOSE_TOPIC_TIMER_CUSTOM_FIELD
|
||||
] = topic_timer.id
|
||||
topic.custom_fields[AUTO_CLOSE_TOPIC_TIMER_CUSTOM_FIELD] = topic_timer.id
|
||||
|
||||
MessageBus.publish("/topic/#{topic.id}", reload_topic: true)
|
||||
end
|
||||
|
@ -182,20 +177,18 @@ SQL
|
|||
post.save!
|
||||
|
||||
# TODO remove_action! does not allow for this type of interface
|
||||
if defined? UserAction::SOLVED
|
||||
UserAction.where(
|
||||
action_type: UserAction::SOLVED,
|
||||
target_post_id: post.id
|
||||
).destroy_all
|
||||
if defined?(UserAction::SOLVED)
|
||||
UserAction.where(action_type: UserAction::SOLVED, target_post_id: post.id).destroy_all
|
||||
end
|
||||
|
||||
# yank notification
|
||||
notification = Notification.find_by(
|
||||
notification_type: Notification.types[:custom],
|
||||
user_id: post.user_id,
|
||||
topic_id: post.topic_id,
|
||||
post_number: post.post_number
|
||||
)
|
||||
notification =
|
||||
Notification.find_by(
|
||||
notification_type: Notification.types[:custom],
|
||||
user_id: post.user_id,
|
||||
topic_id: post.topic_id,
|
||||
post_number: post.post_number,
|
||||
)
|
||||
|
||||
notification.destroy! if notification
|
||||
|
||||
|
@ -212,7 +205,6 @@ SQL
|
|||
require_dependency "application_controller"
|
||||
|
||||
class DiscourseSolved::AnswerController < ::ApplicationController
|
||||
|
||||
def accept
|
||||
limit_accepts
|
||||
|
||||
|
@ -254,14 +246,13 @@ SQL
|
|||
post "/unaccept" => "answer#unaccept"
|
||||
end
|
||||
|
||||
Discourse::Application.routes.append do
|
||||
mount ::DiscourseSolved::Engine, at: "solution"
|
||||
end
|
||||
Discourse::Application.routes.append { mount ::DiscourseSolved::Engine, at: "solution" }
|
||||
|
||||
topic_view_post_custom_fields_allowlister { ["is_accepted_answer"] }
|
||||
|
||||
def get_schema_text(post)
|
||||
post.excerpt(nil, keep_onebox_body: true).presence || post.excerpt(nil, keep_onebox_body: true, keep_quotes: true)
|
||||
post.excerpt(nil, keep_onebox_body: true).presence ||
|
||||
post.excerpt(nil, keep_onebox_body: true, keep_quotes: true)
|
||||
end
|
||||
|
||||
def before_head_close_meta(controller)
|
||||
|
@ -276,59 +267,63 @@ SQL
|
|||
|
||||
return "" if SiteSetting.solved_add_schema_markup == "never"
|
||||
|
||||
allowed = controller
|
||||
.guardian
|
||||
.allow_accepted_answers?(
|
||||
topic.category_id, topic.tags.pluck(:name)
|
||||
)
|
||||
allowed =
|
||||
controller.guardian.allow_accepted_answers?(topic.category_id, topic.tags.pluck(:name))
|
||||
return "" if !allowed
|
||||
|
||||
first_post = topic_view.posts&.first
|
||||
return "" if first_post&.post_number != 1
|
||||
|
||||
question_json = {
|
||||
'@type' => 'Question',
|
||||
'name' => topic.title,
|
||||
'text' => get_schema_text(first_post),
|
||||
'upvoteCount' => first_post.like_count,
|
||||
'answerCount' => 0,
|
||||
'dateCreated' => topic.created_at,
|
||||
'author' => {
|
||||
'@type' => 'Person',
|
||||
'name' => topic.user&.name
|
||||
}
|
||||
"@type" => "Question",
|
||||
"name" => topic.title,
|
||||
"text" => get_schema_text(first_post),
|
||||
"upvoteCount" => first_post.like_count,
|
||||
"answerCount" => 0,
|
||||
"dateCreated" => topic.created_at,
|
||||
"author" => {
|
||||
"@type" => "Person",
|
||||
"name" => topic.user&.name,
|
||||
},
|
||||
}
|
||||
|
||||
if accepted_answer = Post.find_by(id: topic.custom_fields["accepted_answer_post_id"])
|
||||
question_json['answerCount'] = 1
|
||||
question_json["answerCount"] = 1
|
||||
question_json[:acceptedAnswer] = {
|
||||
'@type' => 'Answer',
|
||||
'text' => get_schema_text(accepted_answer),
|
||||
'upvoteCount' => accepted_answer.like_count,
|
||||
'dateCreated' => accepted_answer.created_at,
|
||||
'url' => accepted_answer.full_url,
|
||||
'author' => {
|
||||
'@type' => 'Person',
|
||||
'name' => accepted_answer.user&.username
|
||||
}
|
||||
"@type" => "Answer",
|
||||
"text" => get_schema_text(accepted_answer),
|
||||
"upvoteCount" => accepted_answer.like_count,
|
||||
"dateCreated" => accepted_answer.created_at,
|
||||
"url" => accepted_answer.full_url,
|
||||
"author" => {
|
||||
"@type" => "Person",
|
||||
"name" => accepted_answer.user&.username,
|
||||
},
|
||||
}
|
||||
else
|
||||
return "" if SiteSetting.solved_add_schema_markup == "answered only"
|
||||
end
|
||||
|
||||
['<script type="application/ld+json">', MultiJson.dump(
|
||||
'@context' => 'http://schema.org',
|
||||
'@type' => 'QAPage',
|
||||
'name' => topic&.title,
|
||||
'mainEntity' => question_json
|
||||
).gsub("</", "<\\/").html_safe, '</script>'].join("")
|
||||
[
|
||||
'<script type="application/ld+json">',
|
||||
MultiJson
|
||||
.dump(
|
||||
"@context" => "http://schema.org",
|
||||
"@type" => "QAPage",
|
||||
"name" => topic&.title,
|
||||
"mainEntity" => question_json,
|
||||
)
|
||||
.gsub("</", "<\\/")
|
||||
.html_safe,
|
||||
"</script>",
|
||||
].join("")
|
||||
end
|
||||
|
||||
register_html_builder('server:before-head-close-crawler') do |controller|
|
||||
register_html_builder("server:before-head-close-crawler") do |controller|
|
||||
before_head_close_meta(controller)
|
||||
end
|
||||
|
||||
register_html_builder('server:before-head-close') do |controller|
|
||||
register_html_builder("server:before-head-close") do |controller|
|
||||
before_head_close_meta(controller)
|
||||
end
|
||||
|
||||
|
@ -341,39 +336,42 @@ SQL
|
|||
category_id, include_subcategories = report.add_category_filter
|
||||
if category_id
|
||||
if include_subcategories
|
||||
accepted_solutions = accepted_solutions.joins(:topic).where('topics.category_id IN (?)', Category.subcategory_ids(category_id))
|
||||
accepted_solutions =
|
||||
accepted_solutions.joins(:topic).where(
|
||||
"topics.category_id IN (?)",
|
||||
Category.subcategory_ids(category_id),
|
||||
)
|
||||
else
|
||||
accepted_solutions = accepted_solutions.joins(:topic).where('topics.category_id = ?', category_id)
|
||||
accepted_solutions =
|
||||
accepted_solutions.joins(:topic).where("topics.category_id = ?", category_id)
|
||||
end
|
||||
end
|
||||
|
||||
accepted_solutions.where("topic_custom_fields.created_at >= ?", report.start_date)
|
||||
accepted_solutions
|
||||
.where("topic_custom_fields.created_at >= ?", report.start_date)
|
||||
.where("topic_custom_fields.created_at <= ?", report.end_date)
|
||||
.group("DATE(topic_custom_fields.created_at)")
|
||||
.order("DATE(topic_custom_fields.created_at)")
|
||||
.count
|
||||
.each do |date, count|
|
||||
report.data << { x: date, y: count }
|
||||
end
|
||||
.each { |date, count| report.data << { x: date, y: count } }
|
||||
report.total = accepted_solutions.count
|
||||
report.prev30Days = accepted_solutions.where("topic_custom_fields.created_at >= ?", report.start_date - 30.days)
|
||||
.where("topic_custom_fields.created_at <= ?", report.start_date)
|
||||
.count
|
||||
report.prev30Days =
|
||||
accepted_solutions
|
||||
.where("topic_custom_fields.created_at >= ?", report.start_date - 30.days)
|
||||
.where("topic_custom_fields.created_at <= ?", report.start_date)
|
||||
.count
|
||||
end
|
||||
end
|
||||
|
||||
if defined?(UserAction::SOLVED)
|
||||
require_dependency 'user_summary'
|
||||
require_dependency "user_summary"
|
||||
class ::UserSummary
|
||||
def solved_count
|
||||
UserAction
|
||||
.where(user: @user)
|
||||
.where(action_type: UserAction::SOLVED)
|
||||
.count
|
||||
UserAction.where(user: @user).where(action_type: UserAction::SOLVED).count
|
||||
end
|
||||
end
|
||||
|
||||
require_dependency 'user_summary_serializer'
|
||||
require_dependency "user_summary_serializer"
|
||||
class ::UserSummarySerializer
|
||||
attributes :solved_count
|
||||
|
||||
|
@ -385,20 +383,22 @@ SQL
|
|||
|
||||
class ::WebHook
|
||||
def self.enqueue_solved_hooks(event, post, payload = nil)
|
||||
if active_web_hooks('solved').exists? && post.present?
|
||||
if active_web_hooks("solved").exists? && post.present?
|
||||
payload ||= WebHook.generate_payload(:post, post)
|
||||
|
||||
WebHook.enqueue_hooks(:solved, event,
|
||||
WebHook.enqueue_hooks(
|
||||
:solved,
|
||||
event,
|
||||
id: post.id,
|
||||
category_id: post.topic&.category_id,
|
||||
tag_ids: post.topic&.tags&.pluck(:id),
|
||||
payload: payload
|
||||
payload: payload,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require_dependency 'topic_view_serializer'
|
||||
require_dependency "topic_view_serializer"
|
||||
class ::TopicViewSerializer
|
||||
attributes :accepted_answer
|
||||
|
||||
|
@ -408,20 +408,18 @@ SQL
|
|||
|
||||
def accepted_answer
|
||||
if info = accepted_answer_post_info
|
||||
{
|
||||
post_number: info[0],
|
||||
username: info[1],
|
||||
excerpt: info[2]
|
||||
}
|
||||
{ post_number: info[0], username: info[1], excerpt: info[2] }
|
||||
end
|
||||
end
|
||||
|
||||
def accepted_answer_post_info
|
||||
# TODO: we may already have it in the stream ... so bypass query here
|
||||
postInfo = Post.where(id: accepted_answer_post_id, topic_id: object.topic.id)
|
||||
.joins(:user)
|
||||
.pluck('post_number', 'username', 'cooked')
|
||||
.first
|
||||
postInfo =
|
||||
Post
|
||||
.where(id: accepted_answer_post_id, topic_id: object.topic.id)
|
||||
.joins(:user)
|
||||
.pluck("post_number", "username", "cooked")
|
||||
.first
|
||||
|
||||
if postInfo
|
||||
postInfo[2] = if SiteSetting.solved_quote_length > 0
|
||||
|
@ -436,40 +434,42 @@ SQL
|
|||
def accepted_answer_post_id
|
||||
id = object.topic.custom_fields["accepted_answer_post_id"]
|
||||
# a bit messy but race conditions can give us an array here, avoid
|
||||
id && id.to_i rescue nil
|
||||
begin
|
||||
id && id.to_i
|
||||
rescue StandardError
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class ::Category
|
||||
after_save :reset_accepted_cache
|
||||
|
||||
protected
|
||||
|
||||
def reset_accepted_cache
|
||||
::Guardian.reset_accepted_answer_cache
|
||||
end
|
||||
end
|
||||
|
||||
class ::Guardian
|
||||
|
||||
@@allowed_accepted_cache = DistributedCache.new("allowed_accepted")
|
||||
|
||||
def self.reset_accepted_answer_cache
|
||||
@@allowed_accepted_cache["allowed"] =
|
||||
begin
|
||||
Set.new(
|
||||
CategoryCustomField
|
||||
.where(name: "enable_accepted_answers", value: "true")
|
||||
.pluck(:category_id)
|
||||
)
|
||||
end
|
||||
@@allowed_accepted_cache["allowed"] = begin
|
||||
Set.new(
|
||||
CategoryCustomField.where(name: "enable_accepted_answers", value: "true").pluck(
|
||||
:category_id,
|
||||
),
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def allow_accepted_answers?(category_id, tag_names = [])
|
||||
return true if SiteSetting.allow_solved_on_all_topics
|
||||
|
||||
if SiteSetting.enable_solved_tags.present? && tag_names.present?
|
||||
allowed_tags = SiteSetting.enable_solved_tags.split('|')
|
||||
allowed_tags = SiteSetting.enable_solved_tags.split("|")
|
||||
is_allowed = (tag_names & allowed_tags).present?
|
||||
|
||||
return true if is_allowed
|
||||
|
@ -496,7 +496,7 @@ SQL
|
|||
end
|
||||
end
|
||||
|
||||
require_dependency 'post_serializer'
|
||||
require_dependency "post_serializer"
|
||||
class ::PostSerializer
|
||||
attributes :can_accept_answer, :can_unaccept_answer, :accepted_answer
|
||||
|
||||
|
@ -510,67 +510,76 @@ SQL
|
|||
|
||||
def can_unaccept_answer
|
||||
if topic = (topic_view && topic_view.topic) || object.topic
|
||||
scope.can_accept_answer?(topic, object) && (post_custom_fields["is_accepted_answer"] == 'true')
|
||||
scope.can_accept_answer?(topic, object) &&
|
||||
(post_custom_fields["is_accepted_answer"] == "true")
|
||||
end
|
||||
end
|
||||
|
||||
def accepted_answer
|
||||
post_custom_fields["is_accepted_answer"] == 'true'
|
||||
post_custom_fields["is_accepted_answer"] == "true"
|
||||
end
|
||||
end
|
||||
|
||||
require_dependency 'search'
|
||||
require_dependency "search"
|
||||
|
||||
#TODO Remove when plugin is 1.0
|
||||
if Search.respond_to? :advanced_filter
|
||||
Search.advanced_filter(/status:solved/) do |posts|
|
||||
posts.where("topics.id IN (
|
||||
posts.where(
|
||||
"topics.id IN (
|
||||
SELECT tc.topic_id
|
||||
FROM topic_custom_fields tc
|
||||
WHERE tc.name = 'accepted_answer_post_id' AND
|
||||
tc.value IS NOT NULL
|
||||
)")
|
||||
|
||||
)",
|
||||
)
|
||||
end
|
||||
|
||||
Search.advanced_filter(/status:unsolved/) do |posts|
|
||||
posts.where("topics.id NOT IN (
|
||||
posts.where(
|
||||
"topics.id NOT IN (
|
||||
SELECT tc.topic_id
|
||||
FROM topic_custom_fields tc
|
||||
WHERE tc.name = 'accepted_answer_post_id' AND
|
||||
tc.value IS NOT NULL
|
||||
)")
|
||||
|
||||
)",
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
if Discourse.has_needed_version?(Discourse::VERSION::STRING, '1.8.0.beta6')
|
||||
require_dependency 'topic_query'
|
||||
if Discourse.has_needed_version?(Discourse::VERSION::STRING, "1.8.0.beta6")
|
||||
require_dependency "topic_query"
|
||||
|
||||
TopicQuery.add_custom_filter(:solved) do |results, topic_query|
|
||||
if topic_query.options[:solved] == 'yes'
|
||||
results = results.where("topics.id IN (
|
||||
if topic_query.options[:solved] == "yes"
|
||||
results =
|
||||
results.where(
|
||||
"topics.id IN (
|
||||
SELECT tc.topic_id
|
||||
FROM topic_custom_fields tc
|
||||
WHERE tc.name = 'accepted_answer_post_id' AND
|
||||
tc.value IS NOT NULL
|
||||
)")
|
||||
elsif topic_query.options[:solved] == 'no'
|
||||
results = results.where("topics.id NOT IN (
|
||||
)",
|
||||
)
|
||||
elsif topic_query.options[:solved] == "no"
|
||||
results =
|
||||
results.where(
|
||||
"topics.id NOT IN (
|
||||
SELECT tc.topic_id
|
||||
FROM topic_custom_fields tc
|
||||
WHERE tc.name = 'accepted_answer_post_id' AND
|
||||
tc.value IS NOT NULL
|
||||
)")
|
||||
)",
|
||||
)
|
||||
end
|
||||
results
|
||||
end
|
||||
end
|
||||
|
||||
require_dependency 'topic_list_item_serializer'
|
||||
require_dependency 'search_topic_list_item_serializer'
|
||||
require_dependency 'suggested_topic_serializer'
|
||||
require_dependency 'user_summary_serializer'
|
||||
require_dependency "topic_list_item_serializer"
|
||||
require_dependency "search_topic_list_item_serializer"
|
||||
require_dependency "suggested_topic_serializer"
|
||||
require_dependency "user_summary_serializer"
|
||||
|
||||
class ::TopicListItemSerializer
|
||||
include TopicAnswerMixin
|
||||
|
@ -592,16 +601,21 @@ SQL
|
|||
include TopicAnswerMixin
|
||||
end
|
||||
|
||||
TopicList.preloaded_custom_fields << "accepted_answer_post_id" if TopicList.respond_to? :preloaded_custom_fields
|
||||
Site.preloaded_category_custom_fields << "enable_accepted_answers" if Site.respond_to? :preloaded_category_custom_fields
|
||||
Search.preloaded_topic_custom_fields << "accepted_answer_post_id" if Search.respond_to? :preloaded_topic_custom_fields
|
||||
if TopicList.respond_to? :preloaded_custom_fields
|
||||
TopicList.preloaded_custom_fields << "accepted_answer_post_id"
|
||||
end
|
||||
if Site.respond_to? :preloaded_category_custom_fields
|
||||
Site.preloaded_category_custom_fields << "enable_accepted_answers"
|
||||
end
|
||||
if Search.respond_to? :preloaded_topic_custom_fields
|
||||
Search.preloaded_topic_custom_fields << "accepted_answer_post_id"
|
||||
end
|
||||
|
||||
if CategoryList.respond_to?(:preloaded_topic_custom_fields)
|
||||
CategoryList.preloaded_topic_custom_fields << "accepted_answer_post_id"
|
||||
end
|
||||
|
||||
on(:filter_auto_bump_topics) do |_category, filters|
|
||||
filters.push(->(r) { r.where(<<~SQL)
|
||||
on(:filter_auto_bump_topics) { |_category, filters| filters.push(->(r) { r.where(<<~SQL) }) }
|
||||
NOT EXISTS(
|
||||
SELECT 1 FROM topic_custom_fields
|
||||
WHERE topic_id = topics.id
|
||||
|
@ -609,12 +623,10 @@ SQL
|
|||
AND value IS NOT NULL
|
||||
)
|
||||
SQL
|
||||
})
|
||||
end
|
||||
|
||||
on(:before_post_publish_changes) do |post_changes, topic_changes, options|
|
||||
category_id_changes = topic_changes.diff['category_id'].to_a
|
||||
tag_changes = topic_changes.diff['tags'].to_a
|
||||
category_id_changes = topic_changes.diff["category_id"].to_a
|
||||
tag_changes = topic_changes.diff["tags"].to_a
|
||||
|
||||
old_allowed = Guardian.new.allow_accepted_answers?(category_id_changes[0], tag_changes[0])
|
||||
new_allowed = Guardian.new.allow_accepted_answers?(category_id_changes[1], tag_changes[1])
|
||||
|
@ -628,14 +640,25 @@ SQL
|
|||
if type == :category
|
||||
next if SiteSetting.allow_solved_on_all_topics
|
||||
|
||||
solved_category = DiscourseDev::Record.random(Category.where(read_restricted: false, id: records.pluck(:id), parent_category_id: nil))
|
||||
CategoryCustomField.create!(category_id: solved_category.id, name: "enable_accepted_answers", value: "true")
|
||||
solved_category =
|
||||
DiscourseDev::Record.random(
|
||||
Category.where(read_restricted: false, id: records.pluck(:id), parent_category_id: nil),
|
||||
)
|
||||
CategoryCustomField.create!(
|
||||
category_id: solved_category.id,
|
||||
name: "enable_accepted_answers",
|
||||
value: "true",
|
||||
)
|
||||
puts "discourse-solved enabled on category '#{solved_category.name}' (#{solved_category.id})."
|
||||
elsif type == :topic
|
||||
topics = Topic.where(id: records.pluck(:id))
|
||||
|
||||
unless SiteSetting.allow_solved_on_all_topics
|
||||
solved_category_id = CategoryCustomField.where(name: "enable_accepted_answers", value: "true").first.category_id
|
||||
solved_category_id =
|
||||
CategoryCustomField
|
||||
.where(name: "enable_accepted_answers", value: "true")
|
||||
.first
|
||||
.category_id
|
||||
|
||||
unless topics.exists?(category_id: solved_category_id)
|
||||
topics.last.update(category_id: solved_category_id)
|
||||
|
@ -657,7 +680,8 @@ SQL
|
|||
end
|
||||
end
|
||||
|
||||
query = "
|
||||
query =
|
||||
"
|
||||
WITH x AS (SELECT
|
||||
u.id user_id,
|
||||
COUNT(DISTINCT ua.id) AS solutions
|
||||
|
@ -675,9 +699,7 @@ SQL
|
|||
AND di.period_type = :period_type
|
||||
AND di.solutions <> x.solutions
|
||||
"
|
||||
if respond_to?(:add_directory_column)
|
||||
add_directory_column("solutions", query: query)
|
||||
end
|
||||
add_directory_column("solutions", query: query) if respond_to?(:add_directory_column)
|
||||
|
||||
add_to_class(:composer_messages_finder, :check_topic_is_solved) do
|
||||
return if !SiteSetting.solved_enabled || SiteSetting.disable_solved_education_message
|
||||
|
@ -685,21 +707,18 @@ SQL
|
|||
return if @topic.custom_fields["accepted_answer_post_id"].blank?
|
||||
|
||||
{
|
||||
id: 'solved_topic',
|
||||
templateName: 'education',
|
||||
id: "solved_topic",
|
||||
templateName: "education",
|
||||
wait_for_typing: true,
|
||||
extraClass: 'education-message',
|
||||
extraClass: "education-message",
|
||||
hide_if_whisper: true,
|
||||
body: PrettyText.cook(I18n.t('education.topic_is_solved', base_url: Discourse.base_url))
|
||||
body: PrettyText.cook(I18n.t("education.topic_is_solved", base_url: Discourse.base_url)),
|
||||
}
|
||||
end
|
||||
|
||||
if defined?(UserAction::SOLVED)
|
||||
add_to_serializer(:user_card, :accepted_answers) do
|
||||
UserAction
|
||||
.where(user_id: object.id)
|
||||
.where(action_type: UserAction::SOLVED)
|
||||
.count
|
||||
UserAction.where(user_id: object.id).where(action_type: UserAction::SOLVED).count
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -709,21 +728,19 @@ SQL
|
|||
end
|
||||
|
||||
register_topic_list_preload_user_ids do |topics, user_ids, topic_list|
|
||||
answer_post_ids = TopicCustomField
|
||||
.select('value::INTEGER')
|
||||
.where(name: 'accepted_answer_post_id')
|
||||
.where(topic_id: topics.map(&:id))
|
||||
answer_user_ids = Post
|
||||
.where(id: answer_post_ids)
|
||||
.pluck(:topic_id, :user_id)
|
||||
.to_h
|
||||
answer_post_ids =
|
||||
TopicCustomField
|
||||
.select("value::INTEGER")
|
||||
.where(name: "accepted_answer_post_id")
|
||||
.where(topic_id: topics.map(&:id))
|
||||
answer_user_ids = Post.where(id: answer_post_ids).pluck(:topic_id, :user_id).to_h
|
||||
topics.each { |topic| topic.accepted_answer_user_id = answer_user_ids[topic.id] }
|
||||
user_ids.concat(answer_user_ids.values)
|
||||
end
|
||||
|
||||
module AddSolvedToTopicPostersSummary
|
||||
def descriptions_by_id
|
||||
if !defined? @descriptions_by_id
|
||||
if !defined?(@descriptions_by_id)
|
||||
super(ids: old_user_ids)
|
||||
|
||||
if id = topic.accepted_answer_user_id
|
||||
|
@ -749,7 +766,7 @@ SQL
|
|||
end
|
||||
|
||||
TopicPostersSummary.class_eval do
|
||||
alias :old_user_ids :user_ids
|
||||
alias old_user_ids user_ids
|
||||
|
||||
prepend AddSolvedToTopicPostersSummary
|
||||
end
|
||||
|
@ -762,28 +779,45 @@ SQL
|
|||
# we prefer to abstract logic in service object and test this
|
||||
next if Rails.env.test?
|
||||
|
||||
name = 'first_accepted_solution'
|
||||
DiscourseAutomation::Automation.where(trigger: name, enabled: true).find_each do |automation|
|
||||
maximum_trust_level = automation.trigger_field('maximum_trust_level')&.dig('value')
|
||||
if FirstAcceptedPostSolutionValidator.check(post, trust_level: maximum_trust_level)
|
||||
automation.trigger!(
|
||||
'kind' => name,
|
||||
'accepted_post_id' => post.id,
|
||||
'usernames' => [post.user.username],
|
||||
'placeholders' => {
|
||||
'post_url' => Discourse.base_url + post.url
|
||||
}
|
||||
)
|
||||
name = "first_accepted_solution"
|
||||
DiscourseAutomation::Automation
|
||||
.where(trigger: name, enabled: true)
|
||||
.find_each do |automation|
|
||||
maximum_trust_level = automation.trigger_field("maximum_trust_level")&.dig("value")
|
||||
if FirstAcceptedPostSolutionValidator.check(post, trust_level: maximum_trust_level)
|
||||
automation.trigger!(
|
||||
"kind" => name,
|
||||
"accepted_post_id" => post.id,
|
||||
"usernames" => [post.user.username],
|
||||
"placeholders" => {
|
||||
"post_url" => Discourse.base_url + post.url,
|
||||
},
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
TRUST_LEVELS = [
|
||||
{ id: 1, name: 'discourse_automation.triggerables.first_accepted_solution.max_trust_level.tl1' },
|
||||
{ id: 2, name: 'discourse_automation.triggerables.first_accepted_solution.max_trust_level.tl2' },
|
||||
{ id: 3, name: 'discourse_automation.triggerables.first_accepted_solution.max_trust_level.tl3' },
|
||||
{ id: 4, name: 'discourse_automation.triggerables.first_accepted_solution.max_trust_level.tl4' },
|
||||
{ id: 'any', name: 'discourse_automation.triggerables.first_accepted_solution.max_trust_level.any' },
|
||||
{
|
||||
id: 1,
|
||||
name: "discourse_automation.triggerables.first_accepted_solution.max_trust_level.tl1",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "discourse_automation.triggerables.first_accepted_solution.max_trust_level.tl2",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "discourse_automation.triggerables.first_accepted_solution.max_trust_level.tl3",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "discourse_automation.triggerables.first_accepted_solution.max_trust_level.tl4",
|
||||
},
|
||||
{
|
||||
id: "any",
|
||||
name: "discourse_automation.triggerables.first_accepted_solution.max_trust_level.any",
|
||||
},
|
||||
]
|
||||
|
||||
add_triggerable_to_scriptable(:first_accepted_solution, :send_pms)
|
||||
|
@ -791,7 +825,12 @@ SQL
|
|||
DiscourseAutomation::Triggerable.add(:first_accepted_solution) do
|
||||
placeholder :post_url
|
||||
|
||||
field :maximum_trust_level, component: :choices, extra: { content: TRUST_LEVELS }, required: true
|
||||
field :maximum_trust_level,
|
||||
component: :choices,
|
||||
extra: {
|
||||
content: TRUST_LEVELS,
|
||||
},
|
||||
required: true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,38 +1,55 @@
|
|||
# encoding: utf-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
require 'composer_messages_finder'
|
||||
require "rails_helper"
|
||||
require "composer_messages_finder"
|
||||
|
||||
describe ComposerMessagesFinder do
|
||||
describe '.check_topic_is_solved' do
|
||||
fab!(:user) { Fabricate(:user) }
|
||||
describe ".check_topic_is_solved" do
|
||||
fab!(:user) { Fabricate(:user) }
|
||||
fab!(:topic) { Fabricate(:topic) }
|
||||
fab!(:post) { Fabricate(:post, topic: topic, user: Fabricate(:user)) }
|
||||
|
||||
before do
|
||||
SiteSetting.disable_solved_education_message = false
|
||||
end
|
||||
before { SiteSetting.disable_solved_education_message = false }
|
||||
|
||||
it "does not show message without a topic id" do
|
||||
expect(described_class.new(user, composer_action: 'createTopic').check_topic_is_solved).to be_blank
|
||||
expect(described_class.new(user, composer_action: 'reply').check_topic_is_solved).to be_blank
|
||||
expect(
|
||||
described_class.new(user, composer_action: "createTopic").check_topic_is_solved,
|
||||
).to be_blank
|
||||
expect(described_class.new(user, composer_action: "reply").check_topic_is_solved).to be_blank
|
||||
end
|
||||
|
||||
describe "a reply" do
|
||||
it "does not show message if topic is not solved" do
|
||||
expect(described_class.new(user, composer_action: 'reply', topic_id: topic.id).check_topic_is_solved).to be_blank
|
||||
expect(
|
||||
described_class.new(
|
||||
user,
|
||||
composer_action: "reply",
|
||||
topic_id: topic.id,
|
||||
).check_topic_is_solved,
|
||||
).to be_blank
|
||||
end
|
||||
|
||||
it "does not show message if disable_solved_education_message is true" do
|
||||
SiteSetting.disable_solved_education_message = true
|
||||
DiscourseSolved.accept_answer!(post, Discourse.system_user)
|
||||
expect(described_class.new(user, composer_action: 'reply', topic_id: topic.id).check_topic_is_solved).to be_blank
|
||||
expect(
|
||||
described_class.new(
|
||||
user,
|
||||
composer_action: "reply",
|
||||
topic_id: topic.id,
|
||||
).check_topic_is_solved,
|
||||
).to be_blank
|
||||
end
|
||||
|
||||
it "shows message if the topic is solved" do
|
||||
DiscourseSolved.accept_answer!(post, Discourse.system_user)
|
||||
message = described_class.new(user, composer_action: 'reply', topic_id: topic.id).check_topic_is_solved
|
||||
message =
|
||||
described_class.new(
|
||||
user,
|
||||
composer_action: "reply",
|
||||
topic_id: topic.id,
|
||||
).check_topic_is_solved
|
||||
expect(message).not_to be_blank
|
||||
expect(message[:body]).to include("This topic has been solved")
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
require 'post_revisor'
|
||||
require "rails_helper"
|
||||
require "post_revisor"
|
||||
|
||||
describe PostRevisor do
|
||||
fab!(:category) { Fabricate(:category_with_definition) }
|
||||
|
@ -17,20 +17,22 @@ describe PostRevisor do
|
|||
topic = Fabricate(:topic, category: Fabricate(:category_with_definition))
|
||||
post = Fabricate(:post, topic: topic)
|
||||
|
||||
messages = MessageBus.track_publish("/topic/#{topic.id}") do
|
||||
described_class.new(post).revise!(admin, { category_id: category.id })
|
||||
end
|
||||
messages =
|
||||
MessageBus.track_publish("/topic/#{topic.id}") do
|
||||
described_class.new(post).revise!(admin, { category_id: category.id })
|
||||
end
|
||||
|
||||
expect(messages.first.data[:refresh_stream]).to eq(nil)
|
||||
|
||||
messages = MessageBus.track_publish("/topic/#{topic.id}") do
|
||||
described_class.new(post).revise!(admin, { category_id: category_solved.id })
|
||||
end
|
||||
messages =
|
||||
MessageBus.track_publish("/topic/#{topic.id}") do
|
||||
described_class.new(post).revise!(admin, { category_id: category_solved.id })
|
||||
end
|
||||
|
||||
expect(messages.first.data[:refresh_stream]).to eq(true)
|
||||
end
|
||||
|
||||
describe 'Allowing solved via tags' do
|
||||
describe "Allowing solved via tags" do
|
||||
before do
|
||||
SiteSetting.solved_enabled = true
|
||||
SiteSetting.tagging_enabled = true
|
||||
|
@ -42,22 +44,24 @@ describe PostRevisor do
|
|||
fab!(:topic) { Fabricate(:topic) }
|
||||
let(:post) { Fabricate(:post, topic: topic) }
|
||||
|
||||
it 'sets the refresh option after adding an allowed tag' do
|
||||
it "sets the refresh option after adding an allowed tag" do
|
||||
SiteSetting.enable_solved_tags = tag1.name
|
||||
|
||||
messages = MessageBus.track_publish("/topic/#{topic.id}") do
|
||||
described_class.new(post).revise!(admin, tags: [tag1.name])
|
||||
end
|
||||
messages =
|
||||
MessageBus.track_publish("/topic/#{topic.id}") do
|
||||
described_class.new(post).revise!(admin, tags: [tag1.name])
|
||||
end
|
||||
|
||||
expect(messages.first.data[:refresh_stream]).to eq(true)
|
||||
end
|
||||
|
||||
it 'sets the refresh option if the added tag matches any of the allowed tags' do
|
||||
SiteSetting.enable_solved_tags = [tag1, tag2].map(&:name).join('|')
|
||||
it "sets the refresh option if the added tag matches any of the allowed tags" do
|
||||
SiteSetting.enable_solved_tags = [tag1, tag2].map(&:name).join("|")
|
||||
|
||||
messages = MessageBus.track_publish("/topic/#{topic.id}") do
|
||||
described_class.new(post).revise!(admin, tags: [tag2.name])
|
||||
end
|
||||
messages =
|
||||
MessageBus.track_publish("/topic/#{topic.id}") do
|
||||
described_class.new(post).revise!(admin, tags: [tag2.name])
|
||||
end
|
||||
|
||||
expect(messages.first.data[:refresh_stream]).to eq(true)
|
||||
end
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Fabricator(:solved_web_hook, from: :web_hook) do
|
||||
transient solved_hook: WebHookEventType.find_by(name: 'solved')
|
||||
transient solved_hook: WebHookEventType.find_by(name: "solved")
|
||||
|
||||
after_build do |web_hook, transients|
|
||||
web_hook.web_hook_event_types = [transients[:solved_hook]]
|
||||
end
|
||||
after_build { |web_hook, transients| web_hook.web_hook_event_types = [transients[:solved_hook]] }
|
||||
end
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe "Managing Posts solved status" do
|
||||
let(:topic) { Fabricate(:topic) }
|
||||
let(:user) { Fabricate(:trust_level_4) }
|
||||
let(:p1) { Fabricate(:post, topic: topic) }
|
||||
|
||||
before do
|
||||
SiteSetting.allow_solved_on_all_topics = true
|
||||
end
|
||||
before { SiteSetting.allow_solved_on_all_topics = true }
|
||||
|
||||
describe 'auto bump' do
|
||||
it 'does not automatically bump solved topics' do
|
||||
describe "auto bump" do
|
||||
it "does not automatically bump solved topics" do
|
||||
category = Fabricate(:category_with_definition)
|
||||
|
||||
post = create_post(category: category)
|
||||
|
@ -36,13 +34,13 @@ RSpec.describe "Managing Posts solved status" do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'accepting a post as the answer' do
|
||||
describe "accepting a post as the answer" do
|
||||
before do
|
||||
sign_in(user)
|
||||
SiteSetting.solved_topics_auto_close_hours = 2
|
||||
end
|
||||
|
||||
it 'can mark a post as the accepted answer correctly' do
|
||||
it "can mark a post as the accepted answer correctly" do
|
||||
freeze_time
|
||||
|
||||
post "/solution/accept.json", params: { id: p1.id }
|
||||
|
@ -52,20 +50,18 @@ RSpec.describe "Managing Posts solved status" do
|
|||
|
||||
topic.reload
|
||||
|
||||
expect(topic.public_topic_timer.status_type)
|
||||
.to eq(TopicTimer.types[:silent_close])
|
||||
expect(topic.public_topic_timer.status_type).to eq(TopicTimer.types[:silent_close])
|
||||
|
||||
expect(topic.custom_fields[
|
||||
DiscourseSolved::AUTO_CLOSE_TOPIC_TIMER_CUSTOM_FIELD
|
||||
].to_i).to eq(topic.public_topic_timer.id)
|
||||
expect(topic.custom_fields[DiscourseSolved::AUTO_CLOSE_TOPIC_TIMER_CUSTOM_FIELD].to_i).to eq(
|
||||
topic.public_topic_timer.id,
|
||||
)
|
||||
|
||||
expect(topic.public_topic_timer.execute_at)
|
||||
.to eq_time(Time.zone.now + 2.hours)
|
||||
expect(topic.public_topic_timer.execute_at).to eq_time(Time.zone.now + 2.hours)
|
||||
|
||||
expect(topic.public_topic_timer.based_on_last_post).to eq(true)
|
||||
end
|
||||
|
||||
it 'sends notifications to correct users' do
|
||||
it "sends notifications to correct users" do
|
||||
SiteSetting.notify_on_staff_accept_solved = true
|
||||
user = Fabricate(:user)
|
||||
topic = Fabricate(:topic, user: user)
|
||||
|
@ -74,11 +70,9 @@ RSpec.describe "Managing Posts solved status" do
|
|||
op = topic.user
|
||||
user = post.user
|
||||
|
||||
expect {
|
||||
DiscourseSolved.accept_answer!(post, Discourse.system_user)
|
||||
}.to \
|
||||
change { user.notifications.count }.by(1) &
|
||||
change { op.notifications.count }.by(1)
|
||||
expect { DiscourseSolved.accept_answer!(post, Discourse.system_user) }.to change {
|
||||
user.notifications.count
|
||||
}.by(1) & change { op.notifications.count }.by(1)
|
||||
|
||||
notification = user.notifications.last
|
||||
expect(notification.notification_type).to eq(Notification.types[:custom])
|
||||
|
@ -91,7 +85,7 @@ RSpec.describe "Managing Posts solved status" do
|
|||
expect(notification.post_number).to eq(post.post_number)
|
||||
end
|
||||
|
||||
it 'does not set a timer when the topic is closed' do
|
||||
it "does not set a timer when the topic is closed" do
|
||||
topic.update!(closed: true)
|
||||
post "/solution/accept.json", params: { id: p1.id }
|
||||
|
||||
|
@ -105,7 +99,7 @@ RSpec.describe "Managing Posts solved status" do
|
|||
expect(topic.closed).to eq(true)
|
||||
end
|
||||
|
||||
it 'works with staff and trashed topics' do
|
||||
it "works with staff and trashed topics" do
|
||||
topic.trash!(Discourse.system_user)
|
||||
|
||||
post "/solution/accept.json", params: { id: p1.id }
|
||||
|
@ -119,7 +113,7 @@ RSpec.describe "Managing Posts solved status" do
|
|||
expect(p1.custom_fields["is_accepted_answer"]).to eq("true")
|
||||
end
|
||||
|
||||
it 'does not allow you to accept a whisper' do
|
||||
it "does not allow you to accept a whisper" do
|
||||
whisper = Fabricate(:post, topic: topic, post_type: Post.types[:whisper])
|
||||
sign_in(Fabricate(:admin))
|
||||
|
||||
|
@ -127,7 +121,7 @@ RSpec.describe "Managing Posts solved status" do
|
|||
expect(response.status).to eq(403)
|
||||
end
|
||||
|
||||
it 'triggers a webhook' do
|
||||
it "triggers a webhook" do
|
||||
Fabricate(:solved_web_hook)
|
||||
post "/solution/accept.json", params: { id: p1.id }
|
||||
|
||||
|
@ -139,21 +133,19 @@ RSpec.describe "Managing Posts solved status" do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#unaccept' do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
describe "#unaccept" do
|
||||
before { sign_in(user) }
|
||||
|
||||
describe 'when solved_topics_auto_close_hours is enabled' do
|
||||
describe "when solved_topics_auto_close_hours is enabled" do
|
||||
before do
|
||||
SiteSetting.solved_topics_auto_close_hours = 2
|
||||
DiscourseSolved.accept_answer!(p1, user)
|
||||
end
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
p1.reload
|
||||
|
@ -161,10 +153,9 @@ RSpec.describe "Managing Posts solved status" do
|
|||
expect(p1.custom_fields["is_accepted_answer"]).to eq(nil)
|
||||
expect(p1.topic.custom_fields["accepted_answer_post_id"]).to eq(nil)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
it 'triggers a webhook' do
|
||||
it "triggers a webhook" do
|
||||
Fabricate(:solved_web_hook)
|
||||
post "/solution/unaccept.json", params: { id: p1.id }
|
||||
|
||||
|
@ -176,7 +167,7 @@ RSpec.describe "Managing Posts solved status" do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with group moderators' do
|
||||
context "with group moderators" do
|
||||
fab!(:group_user) { Fabricate(:group_user) }
|
||||
let(:user_gm) { group_user.user }
|
||||
let(:group) { group_user.group }
|
||||
|
@ -187,7 +178,7 @@ RSpec.describe "Managing Posts solved status" do
|
|||
sign_in(user_gm)
|
||||
end
|
||||
|
||||
it 'can accept a solution' do
|
||||
it "can accept a solution" do
|
||||
post "/solution/accept.json", params: { id: p1.id }
|
||||
expect(response.status).to eq(200)
|
||||
end
|
||||
|
|
|
@ -1,71 +1,76 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
require "rails_helper"
|
||||
|
||||
describe FirstAcceptedPostSolutionValidator do
|
||||
fab!(:user_tl1) { Fabricate(:user, trust_level: TrustLevel[1]) }
|
||||
|
||||
context 'when user is under max trust level' do
|
||||
context 'with no post accepted yet' do
|
||||
it 'validates the post' do
|
||||
context "when user is under max trust level" do
|
||||
context "with no post accepted yet" do
|
||||
it "validates the post" do
|
||||
post_1 = create_post(user: user_tl1)
|
||||
expect(described_class.check(post_1, trust_level: TrustLevel[2])).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with already had accepted posts' do
|
||||
context "with already had accepted posts" do
|
||||
before do
|
||||
accepted_post = create_post(user: user_tl1)
|
||||
DiscourseSolved.accept_answer!(accepted_post, Discourse.system_user)
|
||||
end
|
||||
|
||||
it 'doesn’t validate the post' do
|
||||
it "doesn’t validate the post" do
|
||||
post_1 = create_post(user: user_tl1)
|
||||
expect(described_class.check(post_1, trust_level: TrustLevel[2])).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a user is above or equal max trust level' do
|
||||
context 'with no post accepted yet' do
|
||||
it 'doesn’t validate the post' do
|
||||
context "when a user is above or equal max trust level" do
|
||||
context "with no post accepted yet" do
|
||||
it "doesn’t validate the post" do
|
||||
post_1 = create_post(user: user_tl1)
|
||||
expect(described_class.check(post_1, trust_level: TrustLevel[1])).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a post is already accepted' do
|
||||
context "when a post is already accepted" do
|
||||
before do
|
||||
accepted_post = create_post(user: user_tl1)
|
||||
DiscourseSolved.accept_answer!(accepted_post, Discourse.system_user)
|
||||
end
|
||||
|
||||
it 'doesn’t validate the post' do
|
||||
it "doesn’t validate the post" do
|
||||
post_1 = create_post(user: user_tl1)
|
||||
expect(described_class.check(post_1, trust_level: TrustLevel[1])).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when using any trust level' do
|
||||
it 'validates the post' do
|
||||
context "when using any trust level" do
|
||||
it "validates the post" do
|
||||
post_1 = create_post(user: user_tl1)
|
||||
expect(described_class.check(post_1, trust_level: 'any')).to eq(true)
|
||||
expect(described_class.check(post_1, trust_level: "any")).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is system' do
|
||||
it 'doesn’t validate the post' do
|
||||
context "when user is system" do
|
||||
it "doesn’t validate the post" do
|
||||
post_1 = create_post(user: Discourse.system_user)
|
||||
expect(described_class.check(post_1, trust_level: 'any')).to eq(false)
|
||||
expect(described_class.check(post_1, trust_level: "any")).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when post is a PM' do
|
||||
it 'doesn’t validate the post' do
|
||||
context "when post is a PM" do
|
||||
it "doesn’t validate the post" do
|
||||
Group.refresh_automatic_groups!
|
||||
post_1 = create_post(user: user_tl1, target_usernames: [user_tl1.username], archetype: Archetype.private_message)
|
||||
expect(described_class.check(post_1, trust_level: 'any')).to eq(false)
|
||||
post_1 =
|
||||
create_post(
|
||||
user: user_tl1,
|
||||
target_usernames: [user_tl1.username],
|
||||
archetype: Archetype.private_message,
|
||||
)
|
||||
expect(described_class.check(post_1, trust_level: "any")).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
require_dependency 'site'
|
||||
require "rails_helper"
|
||||
require_dependency "site"
|
||||
|
||||
describe Site do
|
||||
let(:category) { Fabricate(:category) }
|
||||
let(:guardian) { Guardian.new }
|
||||
|
||||
before do
|
||||
SiteSetting.show_filter_by_solved_status = true
|
||||
end
|
||||
before { SiteSetting.show_filter_by_solved_status = true }
|
||||
|
||||
it "includes `enable_accepted_answers` custom field for categories" do
|
||||
category.custom_fields["enable_accepted_answers"] = true
|
||||
|
|
|
@ -1,21 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe ListController do
|
||||
fab!(:p1) { Fabricate(:post) }
|
||||
fab!(:p2) { Fabricate(:post, topic: p1.topic) }
|
||||
fab!(:p3) { Fabricate(:post, topic: p1.topic) }
|
||||
|
||||
before do
|
||||
SiteSetting.allow_solved_on_all_topics = true
|
||||
end
|
||||
before { SiteSetting.allow_solved_on_all_topics = true }
|
||||
|
||||
it 'shows the user who posted the accepted answer second' do
|
||||
it "shows the user who posted the accepted answer second" do
|
||||
TopicFeaturedUsers.ensure_consistency!
|
||||
DiscourseSolved.accept_answer!(p3, p1.user, topic: p1.topic)
|
||||
|
||||
get '/latest.json'
|
||||
get "/latest.json"
|
||||
posters = response.parsed_body["topic_list"]["topics"].first["posters"]
|
||||
expect(posters[0]["user_id"]).to eq(p1.user_id)
|
||||
expect(posters[1]["user_id"]).to eq(p3.user_id)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe TopicsController do
|
||||
let(:p1) { Fabricate(:post, like_count: 1) }
|
||||
|
@ -9,34 +9,37 @@ RSpec.describe TopicsController do
|
|||
|
||||
def schema_json(answerCount)
|
||||
if answerCount > 0
|
||||
answer_json = ',"acceptedAnswer":{"@type":"Answer","text":"%{answer_text}","upvoteCount":%{answer_likes},"dateCreated":"%{answered_at}","url":"%{answer_url}","author":{"@type":"Person","name":"%{username2}"}}' % {
|
||||
answer_text: p2.excerpt,
|
||||
answer_likes: p2.like_count,
|
||||
answered_at: p2.created_at.as_json,
|
||||
answer_url: p2.full_url,
|
||||
username2: p2.user&.username
|
||||
}
|
||||
answer_json =
|
||||
',"acceptedAnswer":{"@type":"Answer","text":"%{answer_text}","upvoteCount":%{answer_likes},"dateCreated":"%{answered_at}","url":"%{answer_url}","author":{"@type":"Person","name":"%{username2}"}}' %
|
||||
{
|
||||
answer_text: p2.excerpt,
|
||||
answer_likes: p2.like_count,
|
||||
answered_at: p2.created_at.as_json,
|
||||
answer_url: p2.full_url,
|
||||
username2: p2.user&.username,
|
||||
}
|
||||
else
|
||||
answer_json = ""
|
||||
end
|
||||
|
||||
'<script type="application/ld+json">{"@context":"http://schema.org","@type":"QAPage","name":"%{title}","mainEntity":{"@type":"Question","name":"%{title}","text":"%{question_text}","upvoteCount":%{question_likes},"answerCount":%{answerCount},"dateCreated":"%{created_at}","author":{"@type":"Person","name":"%{username1}"}%{answer_json}}}</script>' % {
|
||||
title: topic.title,
|
||||
question_text: p1.excerpt,
|
||||
question_likes: p1.like_count,
|
||||
answerCount: answerCount,
|
||||
created_at: topic.created_at.as_json,
|
||||
username1: topic.user&.name,
|
||||
answer_json: answer_json
|
||||
}
|
||||
# rubocop:todo Layout/LineLength
|
||||
'<script type="application/ld+json">{"@context":"http://schema.org","@type":"QAPage","name":"%{title}","mainEntity":{"@type":"Question","name":"%{title}","text":"%{question_text}","upvoteCount":%{question_likes},"answerCount":%{answerCount},"dateCreated":"%{created_at}","author":{"@type":"Person","name":"%{username1}"}%{answer_json}}}</script>' %
|
||||
# rubocop:enable Layout/LineLength
|
||||
{
|
||||
title: topic.title,
|
||||
question_text: p1.excerpt,
|
||||
question_likes: p1.like_count,
|
||||
answerCount: answerCount,
|
||||
created_at: topic.created_at.as_json,
|
||||
username1: topic.user&.name,
|
||||
answer_json: answer_json,
|
||||
}
|
||||
end
|
||||
|
||||
context 'with solved enabled on every topic' do
|
||||
before do
|
||||
SiteSetting.allow_solved_on_all_topics = true
|
||||
end
|
||||
context "with solved enabled on every topic" do
|
||||
before { SiteSetting.allow_solved_on_all_topics = true }
|
||||
|
||||
it 'should include correct schema information in header' do
|
||||
it "should include correct schema information in header" do
|
||||
get "/t/#{topic.slug}/#{topic.id}"
|
||||
|
||||
expect(response.body).to include(schema_json(0))
|
||||
|
@ -51,7 +54,7 @@ RSpec.describe TopicsController do
|
|||
expect(response.body).to include(schema_json(1))
|
||||
end
|
||||
|
||||
it 'should include quoted content in schema information' do
|
||||
it "should include quoted content in schema information" do
|
||||
post = topic.first_post
|
||||
post.raw = "[quote]This is a quoted text.[/quote]"
|
||||
post.save!
|
||||
|
@ -63,12 +66,12 @@ RSpec.describe TopicsController do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with solved enabled for topics with specific tags' do
|
||||
context "with solved enabled for topics with specific tags" do
|
||||
let(:tag) { Fabricate(:tag) }
|
||||
|
||||
before { SiteSetting.enable_solved_tags = tag.name }
|
||||
|
||||
it 'includes the correct schema information' do
|
||||
it "includes the correct schema information" do
|
||||
DiscourseTagging.add_or_create_tags_by_name(topic, [tag.name])
|
||||
p2.custom_fields["is_accepted_answer"] = true
|
||||
p2.save_custom_fields
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
require "rails_helper"
|
||||
|
||||
describe TopicAnswerMixin do
|
||||
let(:topic) { Fabricate(:topic) }
|
||||
|
@ -17,7 +17,7 @@ describe TopicAnswerMixin do
|
|||
TopicListItemSerializer,
|
||||
SearchTopicListItemSerializer,
|
||||
SuggestedTopicSerializer,
|
||||
UserSummarySerializer::TopicSerializer
|
||||
UserSummarySerializer::TopicSerializer,
|
||||
].each do |serializer|
|
||||
json = serializer.new(topic, scope: guardian, root: false).as_json
|
||||
expect(json[:has_accepted_answer]).to be_truthy
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
require "rails_helper"
|
||||
|
||||
describe UserCardSerializer do
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
|
Loading…
Reference in New Issue