mirror of
https://github.com/discourse/discourse.git
synced 2025-05-03 10:44:35 +08:00

introduces comprehensive statistics tracking for the Discourse Automation plugin, allowing users to monitor the performance and execution patterns of their automations: - Add `discourse_automation_stats` table to track execution metrics including run counts, execution times, and performance data - Create a new `Stat` model to handle tracking and retrieving automation statistics - Update the admin UI to display automation stats (runs today/this week/month and last run time) - Modernize the automation list interface using Glimmer components - Replace the older enable/disable icon with a toggle switch for better UX - Add schema annotations to existing models for better code documentation - Include extensive test coverage for the new statistics functionality This helps administrators understand how their automations are performing and identify potential bottlenecks or optimization opportunities. --------- Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com> Co-authored-by: Ted Johansson <ted@discourse.org>
153 lines
4.3 KiB
Ruby
153 lines
4.3 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module DiscourseAutomation
|
|
class AutomationSerializer < ApplicationSerializer
|
|
attribute :id
|
|
attribute :name
|
|
attribute :enabled
|
|
attribute :script
|
|
attribute :trigger
|
|
attribute :updated_at
|
|
attribute :last_updated_by
|
|
attribute :next_pending_automation_at
|
|
attribute :placeholders
|
|
attribute :stats
|
|
|
|
def last_updated_by
|
|
BasicUserSerializer.new(
|
|
User.find_by(id: object.last_updated_by_id) || Discourse.system_user,
|
|
root: false,
|
|
).as_json
|
|
end
|
|
|
|
def include_next_pending_automation_at?
|
|
object.pending_automations.exists?
|
|
end
|
|
|
|
def next_pending_automation_at
|
|
object&.pending_automations&.first&.execute_at
|
|
end
|
|
|
|
def placeholders
|
|
scriptable_placeholders =
|
|
DiscourseAutomation
|
|
.filter_by_trigger(scriptable&.placeholders || [], object.trigger)
|
|
.map { |placeholder| placeholder[:name] }
|
|
triggerable_placeholders = triggerable&.placeholders || []
|
|
|
|
(scriptable_placeholders + triggerable_placeholders).map do |placeholder|
|
|
placeholder.to_s.gsub(/\s+/, "_").underscore
|
|
end
|
|
end
|
|
|
|
def script
|
|
key = "discourse_automation.scriptables"
|
|
doc_key = "#{key}.#{object.script}.doc"
|
|
script_with_trigger_key = "#{key}.#{object.script}_with_#{object.trigger}.doc"
|
|
|
|
{
|
|
id: object.script,
|
|
version: scriptable.version,
|
|
name:
|
|
I18n.t(
|
|
"#{key}.#{object.script}.title",
|
|
default: "Missing translation for #{key}.#{object.script}.title",
|
|
),
|
|
description: I18n.t("#{key}.#{object.script}.description", default: ""),
|
|
doc: I18n.exists?(doc_key, :en) ? I18n.t(doc_key) : nil,
|
|
with_trigger_doc:
|
|
I18n.exists?(script_with_trigger_key, :en) ? I18n.t(script_with_trigger_key) : nil,
|
|
forced_triggerable: scriptable.forced_triggerable,
|
|
not_found: scriptable.not_found,
|
|
templates:
|
|
process_templates(filter_fields_with_priority(scriptable.fields, object.trigger&.to_sym)),
|
|
fields: process_fields(object.fields.where(target: "script")),
|
|
}
|
|
end
|
|
|
|
def trigger
|
|
key = "discourse_automation.triggerables"
|
|
doc_key = "#{key}.#{object.trigger}.doc"
|
|
|
|
{
|
|
id: object.trigger,
|
|
name:
|
|
I18n.t(
|
|
"#{key}.#{object.trigger}.title",
|
|
default: "Missing translation for #{key}.#{object.trigger}.title",
|
|
),
|
|
description: I18n.t("#{key}.#{object.trigger}.description", default: ""),
|
|
doc: I18n.exists?(doc_key, :en) ? I18n.t(doc_key) : nil,
|
|
not_found: triggerable&.not_found,
|
|
templates: process_templates(triggerable&.fields || []),
|
|
fields: process_fields(object.fields.where(target: "trigger")),
|
|
settings: triggerable&.settings,
|
|
}
|
|
end
|
|
|
|
def include_stats?
|
|
scope&.dig(:stats).present?
|
|
end
|
|
|
|
EMPTY_STATS = {
|
|
total_runs: 0,
|
|
total_time: 0,
|
|
average_run_time: 0,
|
|
min_run_time: 0,
|
|
max_run_time: 0,
|
|
}
|
|
|
|
def stats
|
|
automation_stats = scope&.dig(:stats, object.id) || {}
|
|
|
|
{
|
|
last_day: automation_stats[:last_day] || EMPTY_STATS,
|
|
last_week: automation_stats[:last_week] || EMPTY_STATS,
|
|
last_month: automation_stats[:last_month] || EMPTY_STATS,
|
|
last_run_at: automation_stats[:last_run_at],
|
|
}
|
|
end
|
|
|
|
private
|
|
|
|
def filter_fields_with_priority(arr, trigger)
|
|
unique_with_priority = {}
|
|
|
|
arr.each do |item|
|
|
name = item[:name]
|
|
if (item[:triggerable]&.to_sym == trigger&.to_sym || item[:triggerable].nil?) &&
|
|
(!unique_with_priority.key?(name) || unique_with_priority[name][:triggerable].nil?)
|
|
unique_with_priority[name] = item
|
|
end
|
|
end
|
|
|
|
unique_with_priority.values
|
|
end
|
|
|
|
def process_templates(fields)
|
|
ActiveModel::ArraySerializer.new(
|
|
fields,
|
|
each_serializer: DiscourseAutomation::TemplateSerializer,
|
|
scope: {
|
|
automation: object,
|
|
},
|
|
).as_json
|
|
end
|
|
|
|
def process_fields(fields)
|
|
ActiveModel::ArraySerializer.new(
|
|
fields || [],
|
|
each_serializer: DiscourseAutomation::FieldSerializer,
|
|
).as_json || []
|
|
end
|
|
|
|
def scriptable
|
|
object.scriptable
|
|
end
|
|
|
|
def triggerable
|
|
object.triggerable
|
|
end
|
|
end
|
|
end
|