diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index be49ded7..c445d906 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -118,3 +118,15 @@ en:
label: Skip sending PM if there are no results
attach_csv:
label: Attach the CSV file to the PM
+ recurring_data_explorer_result_topic:
+ fields:
+ topic_id:
+ label: The topic to post query results in
+ query_id:
+ label: Data Explorer Query
+ query_params:
+ label: Data Explorer Query parameters
+ skip_empty:
+ label: Skip posting if there are no results
+ attach_csv:
+ label: Attach the CSV file to the post
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 97044b9f..138e364d 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -6,3 +6,6 @@ en:
recurring_data_explorer_result_pm:
title: Schedule a PM with Data Explorer results
description: Get scheduled reports sent to your messages each month
+ recurring_data_explorer_result_topic:
+ title: Schedule a post in a topic with Data Explorer results
+ description: Get scheduled reports posted to a specific topic each month
diff --git a/lib/discourse_data_explorer/report_generator.rb b/lib/discourse_data_explorer/report_generator.rb
index 3cf27440..3024c547 100644
--- a/lib/discourse_data_explorer/report_generator.rb
+++ b/lib/discourse_data_explorer/report_generator.rb
@@ -18,6 +18,21 @@ def self.generate(query_id, query_params, recipients, opts = {})
build_report_pms(query, table, recipients, attach_csv: opts[:attach_csv], result:)
end
+ def self.generate_post(query_id, query_params, opts = {})
+ query = DiscourseDataExplorer::Query.find(query_id)
+ return {} if !query
+
+ params = params_to_hash(query_params)
+
+ result = DataExplorer.run_query(query, params)
+ query.update!(last_run_at: Time.now)
+
+ return {} if opts[:skip_empty] && result[:pg_result].values.empty?
+ table = ResultToMarkdown.convert(result[:pg_result])
+
+ build_report_post(query, table, attach_csv: opts[:attach_csv], result:)
+ end
+
private
def self.params_to_hash(query_params)
@@ -42,17 +57,7 @@ def self.params_to_hash(query_params)
def self.build_report_pms(query, table = "", targets = [], attach_csv: false, result: nil)
pms = []
- upload =
- if attach_csv
- tmp_filename =
- "#{query.slug}@#{Slug.for(Discourse.current_hostname, "discourse")}-#{Date.today}.dcqresult.csv"
- tmp = Tempfile.new(tmp_filename)
- tmp.write(ResultFormatConverter.convert(:csv, result))
- tmp.rewind
- UploadCreator.new(tmp, tmp_filename, type: "csv_export").create_for(
- Discourse.system_user.id,
- )
- end
+ upload = create_csv_upload(query, result) if attach_csv
targets.each do |target|
name = target[0]
@@ -73,6 +78,33 @@ def self.build_report_pms(query, table = "", targets = [], attach_csv: false, re
pms
end
+ def self.build_report_post(query, table = "", attach_csv: false, result: nil)
+ pms = []
+
+ upload = create_csv_upload(query, result) if attach_csv
+
+ post = {}
+ post["raw"] = "### Scheduled Report for #{query.name}\n\n" +
+ "Query Name:\n#{query.name}\n\nHere are the results:\n#{table}\n\n" +
+ "View query in Data Explorer\n\n" +
+ "Report created at #{Time.zone.now.strftime("%Y-%m-%d at %H:%M:%S")} (#{Time.zone.name})"
+
+ if upload
+ post["raw"] << "\n\nAppendix: [#{upload.original_filename}|attachment](#{upload.short_url})"
+ end
+
+ post
+ end
+
+ def self.create_csv_upload(query, result)
+ tmp_filename =
+ "#{query.slug}@#{Slug.for(Discourse.current_hostname, "discourse")}-#{Date.today}.dcqresult.csv"
+ tmp = Tempfile.new(tmp_filename)
+ tmp.write(ResultFormatConverter.convert(:csv, result))
+ tmp.rewind
+ UploadCreator.new(tmp, tmp_filename, type: "csv_export").create_for(Discourse.system_user.id)
+ end
+
def self.filter_recipients_by_query_access(recipients, query)
users = User.where(username: recipients)
groups = Group.where(name: recipients)
diff --git a/plugin.rb b/plugin.rb
index 6f74277a..0ebe02fe 100644
--- a/plugin.rb
+++ b/plugin.rb
@@ -115,6 +115,60 @@ module ::DiscourseDataExplorer
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: },
+ )
+
+ 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
diff --git a/spec/automation/recurring_data_explorer_result_pm_spec.rb b/spec/automation/recurring_data_explorer_result_pm_spec.rb
index 92f2042a..7852f847 100644
--- a/spec/automation/recurring_data_explorer_result_pm_spec.rb
+++ b/spec/automation/recurring_data_explorer_result_pm_spec.rb
@@ -2,7 +2,7 @@
require "rails_helper"
-describe "RecurringDataExplorerResultPm" do
+describe "recurring data explorer result PM" do
fab!(:admin)
fab!(:user)
diff --git a/spec/automation/recurring_data_explorer_result_topic_spec.rb b/spec/automation/recurring_data_explorer_result_topic_spec.rb
new file mode 100644
index 00000000..19f632b8
--- /dev/null
+++ b/spec/automation/recurring_data_explorer_result_topic_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require "rails_helper"
+
+describe "recurring data explorer result topic" do
+ fab!(:admin)
+
+ fab!(:user)
+ fab!(:another_user) { Fabricate(:user) }
+ fab!(:group_user) { Fabricate(:user) }
+ fab!(:not_allowed_user) { Fabricate(:user) }
+ fab!(:topic)
+
+ fab!(:group) { Fabricate(:group, users: [user, another_user]) }
+ fab!(:another_group) { Fabricate(:group, users: [group_user]) }
+
+ fab!(:automation) do
+ Fabricate(:automation, script: "recurring_data_explorer_result_topic", trigger: "recurring")
+ end
+ fab!(:query)
+ fab!(:query_group) { Fabricate(:query_group, query: query, group: group) }
+ fab!(:query_group) { Fabricate(:query_group, query: query, group: another_group) }
+
+ before do
+ SiteSetting.data_explorer_enabled = true
+ SiteSetting.discourse_automation_enabled = true
+
+ automation.upsert_field!("query_id", "choices", { value: query.id })
+ automation.upsert_field!("topic_id", "text", { value: topic.id })
+ automation.upsert_field!(
+ "query_params",
+ "key-value",
+ { value: [%w[from_days_ago 0], %w[duration_days 15]] },
+ )
+ automation.upsert_field!(
+ "recurrence",
+ "period",
+ { value: { interval: 1, frequency: "day" } },
+ target: "trigger",
+ )
+ automation.upsert_field!("start_date", "date_time", { value: 2.minutes.ago }, target: "trigger")
+ end
+
+ context "when using recurring trigger" do
+ it "sends the post at recurring date_date" do
+ freeze_time 1.day.from_now do
+ expect { Jobs::DiscourseAutomation::Tracker.new.execute }.to change {
+ topic.reload.posts.count
+ }.by(1)
+
+ expect(topic.posts.last.raw).to include("Scheduled Report for #{query.name}")
+ end
+ end
+
+ it "has appropriate content from the report generator" do
+ automation.update(last_updated_by_id: admin.id)
+ automation.trigger!
+
+ expect(topic.reload.posts.last.raw).to include("Query Name:\n#{query.name}")
+ end
+
+ it "does not create the post if skip_empty" do
+ automation.upsert_field!("skip_empty", "boolean", { value: true })
+
+ automation.update(last_updated_by_id: admin.id)
+
+ expect { automation.trigger! }.to_not change { Post.count }
+ end
+ end
+end
diff --git a/spec/report_generator_spec.rb b/spec/report_generator_spec.rb
index b5799552..414c02b9 100644
--- a/spec/report_generator_spec.rb
+++ b/spec/report_generator_spec.rb
@@ -153,4 +153,42 @@
)
end
end
+
+ describe ".generate_post" do
+ it "works without attached csv file" do
+ DiscourseDataExplorer::ResultToMarkdown.expects(:convert).returns("le table")
+ freeze_time
+
+ result = described_class.generate_post(query.id, query_params)
+
+ filename =
+ "#{query.slug}@#{Slug.for(Discourse.current_hostname, "discourse")}-#{Date.today}.dcqresult.csv"
+
+ expect(result["raw"]).to include(
+ "### Scheduled Report for Most Common Likers\n\n" +
+ "Query Name:\n#{query.name}\n\nHere are the results:\nle table\n\n" +
+ "View query in Data Explorer\n\n" +
+ "Report created at #{Time.zone.now.strftime("%Y-%m-%d at %H:%M:%S")} (#{Time.zone.name})",
+ )
+ expect(result["raw"]).not_to include("Appendix")
+ end
+
+ it "works with attached csv file" do
+ DiscourseDataExplorer::ResultToMarkdown.expects(:convert).returns("le table")
+ freeze_time
+
+ result = described_class.generate_post(query.id, query_params, { attach_csv: true })
+
+ filename =
+ "#{query.slug}@#{Slug.for(Discourse.current_hostname, "discourse")}-#{Date.today}.dcqresult.csv"
+
+ expect(result["raw"]).to include(
+ "### Scheduled Report for Most Common Likers\n\n" +
+ "Query Name:\n#{query.name}\n\nHere are the results:\nle table\n\n" +
+ "View query in Data Explorer\n\n" +
+ "Report created at #{Time.zone.now.strftime("%Y-%m-%d at %H:%M:%S")} (#{Time.zone.name})\n\n" +
+ "Appendix: [#{filename}|attachment](upload://",
+ )
+ end
+ end
end