David Battersby 08a01b23fe
FEATURE: add option to send individual report PM to group members (#369)
This change adds an option to the Schedule a PM with Data Explorer results script, which when selected will send individual PMs to each group member selected in the Send to User, Group or Email field.

When this field is not checked, the functionality for groups will work as it did before (ie. send a single PM to the group).
2025-04-18 15:50:57 +04:00

193 lines
6.8 KiB
Ruby

# frozen_string_literal: true
# name: discourse-data-explorer
# about: Allows you to make SQL queries against your live database, allowing for up-to-the-minute stats reporting.
# meta_topic_id: 32566
# version: 0.3
# authors: Riking
# url: https://github.com/discourse/discourse-data-explorer
enabled_site_setting :data_explorer_enabled
register_asset "stylesheets/explorer.scss"
register_svg_icon "caret-down"
register_svg_icon "caret-right"
register_svg_icon "chevron-left"
register_svg_icon "circle-exclamation"
register_svg_icon "info"
register_svg_icon "pencil"
register_svg_icon "upload"
add_admin_route "explorer.title", "explorer"
module ::DiscourseDataExplorer
PLUGIN_NAME = "discourse-data-explorer"
# This should always match the max value for the
# data_explorer_query_result_limit site setting
QUERY_RESULT_MAX_LIMIT = 10_000
end
require_relative "lib/discourse_data_explorer/engine"
after_initialize do
GlobalSetting.add_default(:max_data_explorer_api_reqs_per_10_seconds, 2)
# Available options:
# - warn
# - warn+block
# - block
GlobalSetting.add_default(:max_data_explorer_api_req_mode, "warn")
add_to_class(:guardian, :user_is_a_member_of_group?) do |group|
return false if !current_user
return true if current_user.admin?
current_user.group_ids.include?(group.id)
end
add_to_class(:guardian, :user_can_access_query?) do |query|
return false if !current_user
return true if current_user.admin?
query.groups.blank? || query.groups.any? { |group| user_is_a_member_of_group?(group) }
end
add_to_class(:guardian, :group_and_user_can_access_query?) do |group, query|
return false if !current_user
return true if current_user.admin?
user_is_a_member_of_group?(group) && query.groups.exists?(id: group.id)
end
add_to_serializer(
:group_show,
:has_visible_data_explorer_queries,
include_condition: -> { scope.user_is_a_member_of_group?(object) },
) { DiscourseDataExplorer::Query.for_group(object).exists? }
register_bookmarkable(DiscourseDataExplorer::QueryGroupBookmarkable)
add_api_key_scope(
:discourse_data_explorer,
{ run_queries: { actions: %w[discourse_data_explorer/query#run], params: %i[id] } },
)
reloadable_patch do
if defined?(DiscourseAutomation)
add_automation_scriptable("recurring_data_explorer_result_pm") do
queries =
DiscourseDataExplorer::Query
.where(hidden: false)
.map { |q| { id: q.id, translated_name: q.name } }
field :recipients, component: :email_group_user, required: true
field :query_id, component: :choices, required: true, extra: { content: queries }
field :query_params, component: :"key-value", accepts_placeholders: true
field :skip_empty, component: :boolean
field :users_from_group, component: :boolean
field :attach_csv,
component: :boolean,
validator: ->(attach_csv) do
return if !attach_csv
extensions = SiteSetting.authorized_extensions.split("|")
if (extensions & %w[csv *]).empty?
I18n.t(
"discourse_automation.scriptables.recurring_data_explorer_result_pm.no_csv_allowed",
)
end
end
version 1
triggerables [:recurring]
script do |_, fields, automation|
recipients = Array(fields.dig("recipients", "value")).uniq
query_id = fields.dig("query_id", "value")
query_params = fields.dig("query_params", "value") || {}
skip_empty = fields.dig("skip_empty", "value") || false
users_from_group = fields.dig("users_from_group", "value") || false
attach_csv = fields.dig("attach_csv", "value") || false
unless SiteSetting.data_explorer_enabled
Rails.logger.warn "#{DiscourseDataExplorer::PLUGIN_NAME} - plugin must be enabled to run automation #{automation.id}"
next
end
if recipients.blank?
Rails.logger.warn "#{DiscourseDataExplorer::PLUGIN_NAME} - couldn't find any recipients for automation #{automation.id}"
next
end
DiscourseDataExplorer::ReportGenerator
.generate(
query_id,
query_params,
recipients,
{ skip_empty:, users_from_group:, attach_csv:, render_url_columns: true },
)
.each do |pm|
begin
utils.send_pm(pm, automation_id: automation.id, prefers_encrypt: false)
rescue ActiveRecord::RecordNotSaved => e
Rails.logger.warn "#{DiscourseDataExplorer::PLUGIN_NAME} - couldn't send PM for automation #{automation.id}: #{e.message}"
end
end
end
end
add_automation_scriptable("recurring_data_explorer_result_topic") do
queries =
DiscourseDataExplorer::Query
.where(hidden: false)
.map { |q| { id: q.id, translated_name: q.name } }
field :topic_id, component: :text, required: true
field :query_id, component: :choices, required: true, extra: { content: queries }
field :query_params, component: :"key-value", accepts_placeholders: true
field :skip_empty, component: :boolean
field :attach_csv, component: :boolean
version 1
triggerables [:recurring]
script do |_, fields, automation|
topic_id = fields.dig("topic_id", "value")
query_id = fields.dig("query_id", "value")
query_params = fields.dig("query_params", "value") || {}
skip_empty = fields.dig("skip_empty", "value") || false
attach_csv = fields.dig("attach_csv", "value") || false
unless SiteSetting.data_explorer_enabled
Rails.logger.warn "#{DiscourseDataExplorer::PLUGIN_NAME} - plugin must be enabled to run automation #{automation.id}"
next
end
topic = Topic.find_by(id: topic_id)
if topic.blank?
Rails.logger.warn "#{DiscourseDataExplorer::PLUGIN_NAME} - couldn't find topic ID (#{topic_id}) for automation #{automation.id}"
next
end
begin
post =
DiscourseDataExplorer::ReportGenerator.generate_post(
query_id,
query_params,
{ skip_empty:, attach_csv:, render_url_columns: true },
)
next if post.empty?
PostCreator.create!(
Discourse.system_user,
topic_id: topic.id,
raw: post["raw"],
skip_validations: true,
)
rescue ActiveRecord::RecordNotSaved => e
Rails.logger.warn "#{DiscourseDataExplorer::PLUGIN_NAME} - couldn't reply to topic ID #{topic_id} for automation #{automation.id}: #{e.message}"
end
end
end
end
end
end