committed 10:09AM - 02 Sep 26 UTC
Previously, the events plugin had no way to drive a workflow from an
RSVP or fro…m an event finishing, and a trigger node contributed by any
plugin was added to the registry but never subscribed to its
`DiscourseEvent` — so it saved fine, showed up in the palette, and
silently never fired.
This change wires the subscription into node registration, which drops
the hand-rolled `on(...)` workarounds in assign, topic voting and chat,
and adds `trigger:event_participation_changed` and
`trigger:event_ended`, both scopable to a single topic.
---
Split into two commits, since the first touches shared infrastructure:
- **`FIX: Subscribe trigger events for workflow nodes added by
plugins`** — claiming a node now registers *and* subscribes it, and
contributing plugins are flushed before the host claims its own, so
ownership lands on them. Because the subscription goes through
`Plugin::Instance#on`, a node stops listening while *its own* plugin is
disabled rather than following the host's setting. This also drops a
duplicate registration that let `trigger:chat_message_created` show in
the palette while chat was disabled.
- **`FEATURE: Workflow triggers for event participation and event end`**
— the two triggers. No `on(...)` wiring needed in the events plugin
thanks to the commit above.
Three behaviour changes in the events plugin, each deliberate:
- Withdrawing an RSVP destroyed the record without publishing anything,
so attendance state built from these triggers could never recover from
someone leaving. Removal now publishes, and reads as `status: null` with
`removed: true`. The livestream chat sync skips removals, so its
follow/unfollow behaviour is unchanged.
- The ended occurrence travels with `:discourse_post_event_event_ended`.
`set_next_date` moves the event on immediately afterwards, and
`Event#starts_at` returns `nil` once a bounded series is past
`recurrence_until`, so the event alone cannot say which occurrence
ended.
- Re-submitting an unchanged RSVP still publishes, so the trigger
ignores it rather than running a workflow twice for one decision.
Payload is `{event, post, topic, stats}`, plus `{user, participation}`
on the participation trigger — modelled on
`WebHook.build_calendar_event_payload` rather than `EventSerializer`,
which goes admin-truthy under a system guardian. No JS, no core changes,
no new site settings.
### Known gaps, documented rather than fixed
Bulk invite, `Event#create_invitees` (`insert_all!`),
`reset_invitee_notifications` (`update_all`),
`enforce_private_invitees!` (`delete_all`) and event/user destroy stay
silent. `create_attendance!` swallows `RecordNotUnique`, so a concurrent
first RSVP fires nothing. `event_ended` can re-fire if an edit resets
`finished_at`, and is missed when an event is closed early:
`EventDate.pending` merges `Event.open`, so the in-flight occurrence
leaves the job's scope.
`EventListener` does not rescue `new`/`valid?`/`matches?` and
`DiscourseEvent.trigger` re-raises, so a raising subscriber aborts
`MonitorEventDates` mid-`find_each`. Adding `continue_on_error:` there
breaks existing `track_events` assertions, so it is left for its own
commit.
### Testing
Full `discourse-workflows` and `discourse-events` backend suites pass
(4005 examples), plus the workflow specs in chat, assign and topic
voting. Verified at runtime that all 22 trigger nodes have exactly one
subscription and there are no duplicate registrations, and that both new
nodes disappear from the palette and stop dispatching when
`discourse_events_enabled` is off.
Meta:
https://meta.discourse.org/t/discourse-calendar-events-webhook-triggers-automations-plugin/409623
---------
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>