Skip to content

Commit

Permalink
FEATURE: Add script to post report results in a topic regularly
Browse files Browse the repository at this point in the history
This script is similar to the existing one that schedules
report results to to be sent to a PM on a regular basis,
but instead takes a topic ID and posts to that. This way
people can have report results sent to a public topic regularly
too and not have to deal with PM recipients and so on.
  • Loading branch information
martin-brennan committed Oct 8, 2024
1 parent d306466 commit ee727c7
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 12 deletions.
12 changes: 12 additions & 0 deletions config/locales/client.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions config/locales/server.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
54 changes: 43 additions & 11 deletions lib/discourse_data_explorer/report_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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]
Expand All @@ -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" +
"<a href='#{Discourse.base_url}/admin/plugins/explorer?id=#{query.id}'>View query in Data Explorer</a>\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)
Expand Down
54 changes: 54 additions & 0 deletions plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion spec/automation/recurring_data_explorer_result_pm_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

require "rails_helper"

describe "RecurringDataExplorerResultPm" do
describe "recurring data explorer result PM" do
fab!(:admin)

fab!(:user)
Expand Down
70 changes: 70 additions & 0 deletions spec/automation/recurring_data_explorer_result_topic_spec.rb
Original file line number Diff line number Diff line change
@@ -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
38 changes: 38 additions & 0 deletions spec/report_generator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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" +
"<a href='#{Discourse.base_url}/admin/plugins/explorer?id=#{query.id}'>View query in Data Explorer</a>\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" +
"<a href='#{Discourse.base_url}/admin/plugins/explorer?id=#{query.id}'>View query in Data Explorer</a>\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

0 comments on commit ee727c7

Please sign in to comment.