FEATURE: Merge discourse-automation (#26432)

Automation (previously known as discourse-automation) is now a core plugin.
This commit is contained in:
Osama Sayegh
2024-04-03 18:20:43 +03:00
committed by GitHub
parent 2190c9b957
commit 3d4faf3272
314 changed files with 21182 additions and 10 deletions

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
DiscourseAutomation::Triggerable.add(DiscourseAutomation::Triggers::AFTER_POST_COOK) do
field :restricted_category, component: :category
field :restricted_tags, component: :tags
field :valid_trust_levels, component: :"trust-levels"
end

View File

@ -0,0 +1,3 @@
# frozen_string_literal: true
DiscourseAutomation::Triggerable.add(DiscourseAutomation::Triggers::API_CALL) {}

View File

@ -0,0 +1,5 @@
# frozen_string_literal: true
DiscourseAutomation::Triggerable.add(DiscourseAutomation::Triggers::CATEGORY_CREATED_EDITED) do
field :restricted_category, component: :category
end

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
DiscourseAutomation::Triggerable.add(DiscourseAutomation::Triggers::PM_CREATED) do
field :restricted_user, component: :user
field :restricted_group, component: :group
field :ignore_staff, component: :boolean
field :ignore_automated, component: :boolean
field :ignore_group_members, component: :boolean
field :valid_trust_levels, component: :"trust-levels"
end

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
DiscourseAutomation::Triggerable.add(DiscourseAutomation::Triggers::POINT_IN_TIME) do
field :execute_at, component: :date_time, required: true
on_update do |automation, metadata|
# prevents creating a new pending automation on save when date is expired
execute_at = metadata.dig("execute_at", "value")
if execute_at && execute_at > Time.zone.now
automation.pending_automations.create!(execute_at: execute_at)
end
end
end

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
DiscourseAutomation::Triggerable.add(DiscourseAutomation::Triggers::POST_CREATED_EDITED) do
field :action_type,
component: :choices,
extra: {
content: [
{
id: "created",
name: "discourse_automation.triggerables.post_created_edited.created",
},
{ id: "edited", name: "discourse_automation.triggerables.post_created_edited.edited" },
],
}
field :restricted_category, component: :category
field :restricted_group, component: :group
field :ignore_automated, component: :boolean
field :ignore_group_members, component: :boolean
field :valid_trust_levels, component: :"trust-levels"
field :first_post_only, component: :boolean
field :first_topic_only, component: :boolean
end

View File

@ -0,0 +1,93 @@
# frozen_string_literal: true
module DiscourseAutomation
module Triggers
module Recurring
RECURRENCE_CHOICES = [
{ id: "minute", name: "discourse_automation.triggerables.recurring.frequencies.minute" },
{ id: "hour", name: "discourse_automation.triggerables.recurring.frequencies.hour" },
{ id: "day", name: "discourse_automation.triggerables.recurring.frequencies.day" },
{ id: "weekday", name: "discourse_automation.triggerables.recurring.frequencies.weekday" },
{ id: "week", name: "discourse_automation.triggerables.recurring.frequencies.week" },
{ id: "month", name: "discourse_automation.triggerables.recurring.frequencies.month" },
{ id: "year", name: "discourse_automation.triggerables.recurring.frequencies.year" },
]
def self.setup_pending_automation(automation, fields)
automation.pending_automations.destroy_all
return unless start_date = fields.dig("start_date", "value")
return unless interval = fields.dig("recurrence", "value", "interval")
return unless frequency = fields.dig("recurrence", "value", "frequency")
start_date = Time.parse(start_date)
byday = start_date.strftime("%A").upcase[0, 2]
interval = interval.to_i
interval_end = interval + 1
next_trigger_date =
case frequency
when "minute"
(Time.zone.now + interval.minute).beginning_of_minute
when "hour"
(Time.zone.now + interval.hour).beginning_of_hour
when "day"
RRule::Rule
.new("FREQ=DAILY;INTERVAL=#{interval}", dtstart: start_date)
.between(Time.now, interval_end.days.from_now)
.first
when "weekday"
max_weekends = (interval_end.to_f / 5).ceil
RRule::Rule
.new("FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR", dtstart: start_date)
.between(Time.now.end_of_day, max_weekends.weeks.from_now)
.drop(interval - 1)
.first
when "week"
RRule::Rule
.new("FREQ=WEEKLY;INTERVAL=#{interval};BYDAY=#{byday}", dtstart: start_date)
.between(Time.now.end_of_week, interval_end.weeks.from_now)
.first
when "month"
count = 0
(start_date.beginning_of_month.to_date..start_date.end_of_month.to_date).each do |date|
count += 1 if date.strftime("%A") == start_date.strftime("%A")
break if date.day == start_date.day
end
RRule::Rule
.new("FREQ=MONTHLY;INTERVAL=#{interval};BYDAY=#{count}#{byday}", dtstart: start_date)
.between(Time.now, interval_end.months.from_now)
.first
when "year"
RRule::Rule
.new("FREQ=YEARLY;INTERVAL=#{interval}", dtstart: start_date)
.between(Time.now, interval_end.years.from_now)
.first
end
if next_trigger_date && next_trigger_date > Time.zone.now
automation.pending_automations.create!(execute_at: next_trigger_date)
end
end
end
end
end
DiscourseAutomation::Triggerable.add(DiscourseAutomation::Triggers::RECURRING) do
field :recurrence,
component: :period,
extra: {
content: DiscourseAutomation::Triggers::Recurring::RECURRENCE_CHOICES,
},
required: true
field :start_date, component: :date_time, required: true
on_update do |automation, fields|
DiscourseAutomation::Triggers::Recurring.setup_pending_automation(automation, fields)
end
on_call do |automation, fields|
DiscourseAutomation::Triggers::Recurring.setup_pending_automation(automation, fields)
end
enable_manual_trigger
end

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
key = "discourse_automation.triggerables.stalled_topic.durations"
ids = %w[PT1H P1D P1W P2W P1M P3M P6M P1Y]
duration_choices = ids.map { |id| { id: id, name: "#{key}.#{id}" } }
DiscourseAutomation::Triggerable.add(DiscourseAutomation::Triggers::STALLED_TOPIC) do
field :categories, component: :categories
field :tags, component: :tags
field :stalled_after, component: :choices, extra: { content: duration_choices }, required: true
placeholder :topic_url
end

View File

@ -0,0 +1,37 @@
# frozen_string_literal: true
module DiscourseAutomation
module Triggers
module StalledWiki
DURATION_CHOICES = [
{ id: "PT1H", name: "discourse_automation.triggerables.stalled_wiki.durations.PT1H" },
{ id: "P1D", name: "discourse_automation.triggerables.stalled_wiki.durations.P1D" },
{ id: "P1W", name: "discourse_automation.triggerables.stalled_wiki.durations.P1W" },
{ id: "P2W", name: "discourse_automation.triggerables.stalled_wiki.durations.P2W" },
{ id: "P1M", name: "discourse_automation.triggerables.stalled_wiki.durations.P1M" },
{ id: "P3M", name: "discourse_automation.triggerables.stalled_wiki.durations.P3M" },
{ id: "P6M", name: "discourse_automation.triggerables.stalled_wiki.durations.P6M" },
{ id: "P1Y", name: "discourse_automation.triggerables.stalled_wiki.durations.P1Y" },
]
end
end
end
DiscourseAutomation::Triggerable.add(DiscourseAutomation::Triggers::STALLED_WIKI) do
field :restricted_category, component: :category
field :stalled_after,
component: :choices,
extra: {
content: DiscourseAutomation::Triggers::StalledWiki::DURATION_CHOICES,
},
required: true
field :retriggered_after,
component: :choices,
extra: {
content: DiscourseAutomation::Triggers::StalledWiki::DURATION_CHOICES,
}
placeholder :wiki_url
enable_manual_trigger
end

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
DiscourseAutomation::Triggerable.add(DiscourseAutomation::Triggers::TOPIC) do
field :restricted_topic, component: :text
on_update do |automation, metadata, previous_metadata|
ActiveRecord::Base.transaction do
previous_topic_id = previous_metadata.dig("restricted_topic", "value")
topic_id = metadata.dig("restricted_topic", "value")
if previous_topic_id && previous_topic_id != topic_id
previous_topic = Topic.find_by(id: previous_topic_id)
if previous_topic
TopicCustomField.where(
topic_id: previous_topic_id,
name: DiscourseAutomation::CUSTOM_FIELD,
value: automation.id,
).delete_all
end
end
if topic_id
topic = Topic.find_by(id: topic_id)
topic&.upsert_custom_fields(DiscourseAutomation::CUSTOM_FIELD => automation.id)
end
end
end
end

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
DiscourseAutomation::Triggerable.add(DiscourseAutomation::Triggers::USER_ADDED_TO_GROUP) do
field :joined_group, component: :group, required: true
placeholder :group_name
end

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
DiscourseAutomation::Triggerable.add(DiscourseAutomation::Triggers::USER_BADGE_GRANTED) do
field :badge,
component: :choices,
extra: {
content: Badge.all.map { |b| { id: b.id, translated_name: b.name } },
},
required: true
field :only_first_grant, component: :boolean
placeholder :badge_name
placeholder :grant_count
end

View File

@ -0,0 +1,3 @@
# frozen_string_literal: true
DiscourseAutomation::Triggerable.add(DiscourseAutomation::Triggers::USER_FIRST_LOGGED_IN) {}

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
DiscourseAutomation::Triggerable.add(DiscourseAutomation::Triggers::USER_PROMOTED) do
field :restricted_group, component: :group
field :trust_level_transition,
component: :choices,
extra: {
content: DiscourseAutomation::USER_PROMOTED_TRUST_LEVEL_CHOICES,
},
required: true
placeholder :trust_level_transition
end

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
DiscourseAutomation::Triggerable.add(DiscourseAutomation::Triggers::USER_REMOVED_FROM_GROUP) do
field :left_group, component: :group, required: true
placeholder :group_name
end

View File

@ -0,0 +1,33 @@
# frozen_string_literal: true
class DiscourseAutomation::Triggerable
USER_UPDATED = "user_updated"
end
DiscourseAutomation::Triggerable.add(DiscourseAutomation::Triggers::USER_UPDATED) do
field :custom_fields, component: :custom_fields
field :user_profile, component: :user_profile
field :once_per_user, component: :boolean
validate do
has_triggers = has_trigger_field?(:custom_fields) && has_trigger_field?(:user_profile)
custom_fields = trigger_field(:custom_fields)["value"]
user_profile = trigger_field(:user_profile)["value"]
if has_triggers && custom_fields.blank? && user_profile.blank?
errors.add(
:base,
I18n.t("discourse_automation.triggerables.errors.custom_fields_or_user_profile_required"),
)
false
else
true
end
end
placeholder do |fields, automation|
custom_fields = automation.trigger_field("custom_fields")["value"] || []
user_profile = automation.trigger_field("user_profile")["value"] || []
custom_fields + user_profile
end
end