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