# 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 :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 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:, 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