mirror of
https://github.com/discourse/discourse.git
synced 2026-08-04 10:23:17 -05:00
FEATURE: Introduce set_topic_timer automation script (#36660)
Allows different types of topic-timer to be automatically configured on topics. It uses the 'post_created_edited' trigger, which provides all sorts of filtering options including category/author/etc. <img width="804" height="270" alt="SCR-20251212-mzrg" src="https://github.com/user-attachments/assets/90cca3f8-1516-4adf-830a-c2f43125795d" /> --------- Co-authored-by: Régis Hanol <regis@hanol.fr>
This commit is contained in:
co-authored by
Régis Hanol
parent
cd6c6f8abc
commit
b7a4eadf74
@@ -16,6 +16,7 @@ import DaMessageField from "./fields/da-message-field";
|
||||
import DaPeriodField from "./fields/da-period-field";
|
||||
import DaPmsField from "./fields/da-pms-field";
|
||||
import DaPostField from "./fields/da-post-field";
|
||||
import DaRelativeTimeField from "./fields/da-relative-time-field";
|
||||
import DaTagsField from "./fields/da-tags-field";
|
||||
import DaTextField from "./fields/da-text-field";
|
||||
import DaTextListField from "./fields/da-text-list-field";
|
||||
@@ -48,6 +49,7 @@ const FIELD_COMPONENTS = {
|
||||
email_group_user: DaEmailGroupUserField,
|
||||
custom_field: DaCustomField,
|
||||
custom_fields: DaCustomFields,
|
||||
relative_time: DaRelativeTimeField,
|
||||
};
|
||||
|
||||
export default class AutomationField extends Component {
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import RelativeTimePicker from "discourse/components/relative-time-picker";
|
||||
import BaseField from "./da-base-field";
|
||||
import DAFieldDescription from "./da-field-description";
|
||||
import DAFieldLabel from "./da-field-label";
|
||||
|
||||
export default class RelativeTimeField extends BaseField {
|
||||
<template>
|
||||
<section class="field text-field">
|
||||
<div class="control-group">
|
||||
<DAFieldLabel @label={{@label}} @field={{@field}} />
|
||||
|
||||
<div class="controls">
|
||||
<div class="field-wrapper">
|
||||
<RelativeTimePicker
|
||||
@durationMinutes={{@field.metadata.value}}
|
||||
@onChange={{this.mutValue}}
|
||||
/>
|
||||
|
||||
<DAFieldDescription @description={{@description}} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
}
|
||||
@@ -240,6 +240,11 @@ module DiscourseAutomation
|
||||
},
|
||||
},
|
||||
},
|
||||
"relative_time" => {
|
||||
"value" => {
|
||||
"type" => "integer",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
private
|
||||
|
||||
@@ -434,6 +434,13 @@ en:
|
||||
fields:
|
||||
custom_field_name:
|
||||
label: "User Custom Field name"
|
||||
set_topic_timer:
|
||||
title: Set Topic Timer
|
||||
fields:
|
||||
type:
|
||||
label: Timer Type
|
||||
duration:
|
||||
label: Timer Duration
|
||||
unnamed_automation: "Unnamed automation"
|
||||
|
||||
models:
|
||||
|
||||
@@ -182,3 +182,6 @@ en:
|
||||
random_assign:
|
||||
title: "Random Assign"
|
||||
description: "Randomly assign topics to a group"
|
||||
set_topic_timer:
|
||||
title: "Set Topic Timer"
|
||||
description: "Set a topic timer on a topic"
|
||||
|
||||
@@ -17,6 +17,7 @@ module DiscourseAutomation
|
||||
POST = "post"
|
||||
REMOVE_UPLOAD_MARKUP_FROM_DELETED_POSTS = "remove_upload_markup_from_deleted_posts"
|
||||
SEND_PMS = "send_pms"
|
||||
SET_TOPIC_TIMER = "set_topic_timer"
|
||||
SUSPEND_USER_BY_EMAIL = "suspend_user_by_email"
|
||||
TOPIC = "topic"
|
||||
TOPIC_REQUIRED_WORDS = "topic_required_words"
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
DiscourseAutomation::Scriptable.add(DiscourseAutomation::Scripts::SET_TOPIC_TIMER) do
|
||||
field :type,
|
||||
component: :choices,
|
||||
extra: {
|
||||
content: [
|
||||
{ id: "auto_close", name: "topic.auto_close.title" },
|
||||
{ id: "auto_close_after_last_post", name: "topic.auto_close_after_last_post.title" },
|
||||
{ id: "auto_delete", name: "topic.auto_delete.title" },
|
||||
{ id: "auto_delete_replies", name: "topic.auto_delete_replies.title" },
|
||||
{ id: "auto_bump", name: "topic.auto_bump.title" },
|
||||
],
|
||||
},
|
||||
required: true
|
||||
|
||||
field :duration, component: :relative_time, required: true
|
||||
|
||||
version 1
|
||||
|
||||
triggerables %i[post_created_edited]
|
||||
|
||||
script do |context, fields|
|
||||
post = context["post"]
|
||||
timer_type = fields.dig("type", "value")
|
||||
|
||||
next if !post.topic
|
||||
next unless topic = Topic.find_by(id: post.topic_id)
|
||||
|
||||
duration_minutes = fields.dig("duration", "value")
|
||||
|
||||
case timer_type
|
||||
when "auto_close"
|
||||
topic.set_or_create_timer(
|
||||
TopicTimer.types[:close],
|
||||
(Time.now + duration_minutes.minutes).iso8601,
|
||||
)
|
||||
when "auto_close_after_last_post"
|
||||
topic.set_or_create_timer(
|
||||
TopicTimer.types[:close],
|
||||
nil,
|
||||
based_on_last_post: true,
|
||||
duration_minutes:,
|
||||
)
|
||||
when "auto_delete"
|
||||
topic.set_or_create_timer(
|
||||
TopicTimer.types[:delete],
|
||||
(Time.now + duration_minutes.minutes).iso8601,
|
||||
)
|
||||
when "auto_delete_replies"
|
||||
topic.set_or_create_timer(TopicTimer.types[:delete_replies], nil, duration_minutes:)
|
||||
when "auto_bump"
|
||||
topic.set_or_create_timer(
|
||||
TopicTimer.types[:bump],
|
||||
(Time.now + duration_minutes.minutes).iso8601,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -43,50 +43,12 @@ end
|
||||
require_relative "lib/discourse_automation/engine"
|
||||
|
||||
after_initialize do
|
||||
%w[
|
||||
lib/discourse_automation/scripts
|
||||
lib/discourse_automation/scripts/add_user_to_group_through_custom_field
|
||||
lib/discourse_automation/scripts/append_last_checked_by
|
||||
lib/discourse_automation/scripts/append_last_edited_by
|
||||
lib/discourse_automation/scripts/auto_responder
|
||||
lib/discourse_automation/scripts/auto_tag_topic
|
||||
lib/discourse_automation/scripts/banner_topic
|
||||
lib/discourse_automation/scripts/close_topic
|
||||
lib/discourse_automation/scripts/flag_post_on_words
|
||||
lib/discourse_automation/scripts/email_on_flagged_post
|
||||
lib/discourse_automation/scripts/gift_exchange
|
||||
lib/discourse_automation/scripts/group_category_notification_default
|
||||
lib/discourse_automation/scripts/pin_topic
|
||||
lib/discourse_automation/scripts/post
|
||||
lib/discourse_automation/scripts/remove_upload_markup_from_deleted_posts
|
||||
lib/discourse_automation/scripts/topic
|
||||
lib/discourse_automation/scripts/send_pms
|
||||
lib/discourse_automation/scripts/suspend_user_by_email
|
||||
lib/discourse_automation/scripts/topic_required_words
|
||||
lib/discourse_automation/scripts/user_global_notice
|
||||
lib/discourse_automation/scripts/user_group_membership_through_badge
|
||||
lib/discourse_automation/scripts/zapier_webhook
|
||||
lib/discourse_automation/triggers
|
||||
lib/discourse_automation/triggers/after_post_cook
|
||||
lib/discourse_automation/triggers/api_call
|
||||
lib/discourse_automation/triggers/category_created_edited
|
||||
lib/discourse_automation/triggers/pm_created
|
||||
lib/discourse_automation/triggers/point_in_time
|
||||
lib/discourse_automation/triggers/post_created_edited
|
||||
lib/discourse_automation/triggers/post_flag_created
|
||||
lib/discourse_automation/triggers/recurring
|
||||
lib/discourse_automation/triggers/stalled_topic
|
||||
lib/discourse_automation/triggers/stalled_wiki
|
||||
lib/discourse_automation/triggers/topic_tags_changed
|
||||
lib/discourse_automation/triggers/topic
|
||||
lib/discourse_automation/triggers/topic_closed
|
||||
lib/discourse_automation/triggers/user_added_to_group
|
||||
lib/discourse_automation/triggers/user_badge_granted
|
||||
lib/discourse_automation/triggers/user_promoted
|
||||
lib/discourse_automation/triggers/user_removed_from_group
|
||||
lib/discourse_automation/triggers/user_first_logged_in
|
||||
lib/discourse_automation/triggers/user_updated
|
||||
].each { |path| require_relative path }
|
||||
Dir
|
||||
.glob("./lib/discourse_automation/scripts/*.rb", base: __dir__)
|
||||
.each { |file| require_relative file }
|
||||
Dir
|
||||
.glob("./lib/discourse_automation/triggers/*.rb", base: __dir__)
|
||||
.each { |file| require_relative file }
|
||||
|
||||
reloadable_patch do
|
||||
Post.prepend DiscourseAutomation::PostExtension
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe "SetTopicTimer" do
|
||||
fab!(:automation) do
|
||||
Fabricate(
|
||||
:automation,
|
||||
script: DiscourseAutomation::Scripts::SET_TOPIC_TIMER,
|
||||
trigger: DiscourseAutomation::Triggers::POST_CREATED_EDITED,
|
||||
)
|
||||
end
|
||||
|
||||
def configure_automation(type, duration)
|
||||
automation.fields.create!(
|
||||
component: "choices",
|
||||
name: "type",
|
||||
metadata: {
|
||||
value: type,
|
||||
},
|
||||
target: "script",
|
||||
)
|
||||
automation.fields.create!(
|
||||
component: "relative_time",
|
||||
name: "duration",
|
||||
metadata: {
|
||||
value: duration,
|
||||
},
|
||||
target: "script",
|
||||
)
|
||||
end
|
||||
|
||||
before { SiteSetting.discourse_automation_enabled = true }
|
||||
|
||||
it "handles auto_close timer" do
|
||||
configure_automation("auto_close", 60)
|
||||
|
||||
freeze_time do
|
||||
post =
|
||||
PostCreator.new(
|
||||
Fabricate(:admin),
|
||||
raw: "my new topic",
|
||||
title: "Test topic for timer",
|
||||
).create!
|
||||
|
||||
timer = post.topic.reload.topic_timers[0]
|
||||
expect(timer.status_type).to eq(TopicTimer.types[:close])
|
||||
expect(timer.execute_at).to be_within(1.second).of(60.minutes.from_now)
|
||||
expect(timer.based_on_last_post).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
it "handles auto_close_after_last_post timer" do
|
||||
configure_automation("auto_close_after_last_post", 60)
|
||||
|
||||
freeze_time do
|
||||
post =
|
||||
PostCreator.new(
|
||||
Fabricate(:admin),
|
||||
raw: "my new topic",
|
||||
title: "Test topic for timer",
|
||||
).create!
|
||||
|
||||
timer = post.topic.reload.topic_timers[0]
|
||||
expect(timer.status_type).to eq(TopicTimer.types[:close])
|
||||
expect(timer.execute_at).to be_within(1.second).of(60.minutes.from_now)
|
||||
expect(timer.duration_minutes).to eq(60)
|
||||
expect(timer.based_on_last_post).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
it "handles auto_delete timer" do
|
||||
configure_automation("auto_delete", 60)
|
||||
|
||||
freeze_time do
|
||||
post =
|
||||
PostCreator.new(
|
||||
Fabricate(:admin),
|
||||
raw: "my new topic",
|
||||
title: "Test topic for timer",
|
||||
).create!
|
||||
|
||||
timer = post.topic.reload.topic_timers[0]
|
||||
expect(timer.status_type).to eq(TopicTimer.types[:delete])
|
||||
expect(timer.execute_at).to be_within(1.second).of(60.minutes.from_now)
|
||||
expect(timer.duration_minutes).to eq(nil)
|
||||
expect(timer.based_on_last_post).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
it "handles auto_delete_replies timer" do
|
||||
configure_automation("auto_delete_replies", 60)
|
||||
|
||||
freeze_time do
|
||||
post =
|
||||
PostCreator.new(
|
||||
Fabricate(:admin),
|
||||
raw: "my new topic",
|
||||
title: "Test topic for timer",
|
||||
).create!
|
||||
|
||||
timer = post.topic.reload.topic_timers[0]
|
||||
expect(timer.status_type).to eq(TopicTimer.types[:delete_replies])
|
||||
expect(timer.execute_at).to be_within(1.second).of(60.minutes.from_now)
|
||||
expect(timer.duration_minutes).to eq(60)
|
||||
expect(timer.based_on_last_post).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
it "handles auto_bump timer" do
|
||||
configure_automation("auto_bump", 60)
|
||||
|
||||
freeze_time do
|
||||
post =
|
||||
PostCreator.new(
|
||||
Fabricate(:admin),
|
||||
raw: "my new topic",
|
||||
title: "Test topic for timer",
|
||||
).create!
|
||||
|
||||
timer = post.topic.reload.topic_timers[0]
|
||||
expect(timer.status_type).to eq(TopicTimer.types[:bump])
|
||||
expect(timer.execute_at).to be_within(1.second).of(60.minutes.from_now)
|
||||
expect(timer.duration_minutes).to eq(nil)
|
||||
expect(timer.based_on_last_post).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import { getOwner } from "@ember/owner";
|
||||
import { fillIn, render } from "@ember/test-helpers";
|
||||
import { module, test } from "qunit";
|
||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
||||
import AutomationField from "discourse/plugins/automation/admin/components/automation-field";
|
||||
import AutomationFabricators from "discourse/plugins/automation/admin/lib/fabricators";
|
||||
|
||||
module("Integration | Component | da-relative_time-field", function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.automation = new AutomationFabricators(getOwner(this)).automation();
|
||||
});
|
||||
|
||||
test("set value", async function (assert) {
|
||||
this.field = new AutomationFabricators(getOwner(this)).field({
|
||||
component: "relative_time",
|
||||
});
|
||||
|
||||
await render(
|
||||
<template>
|
||||
<AutomationField
|
||||
@automation={{this.automation}}
|
||||
@field={{this.field}}
|
||||
/>
|
||||
</template>
|
||||
);
|
||||
|
||||
await fillIn(".relative-time-duration", "4");
|
||||
assert.strictEqual(this.field.metadata.value, 4);
|
||||
|
||||
await selectKit().expand();
|
||||
await selectKit().selectRowByValue("hours");
|
||||
|
||||
assert.strictEqual(this.field.metadata.value, 4 * 60);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user