Draft: Enforce the targeted message schedule

What does this MR do and why?

Three fixes to targeted messages, found while prototyping plan targeting in #634504.

  1. TargetedMessageResolver ignored starts_at and ends_at. !216492 (merged) added the schedule so messages display during set windows, but nothing filtered on it, so a message showed as soon as an admin created it and never expired. The resolver now drops messages outside the window.
  2. starts_at_not_in_past ran on every save. Once a message had started, any edit failed validation, including moving the end time to end it early. It now runs only when starts_at changes, and the edit form no longer resends an unchanged start time.
  3. The admin form passed the CSV limit hint to GlFormFields as description, which the component ignores, so the hint never rendered. It now goes in groupAttrs.

Specs now use an :active factory trait instead of relying on the factory's default offsets. That includes the billing feature examples in ee/spec/support/shared_examples/features/targeted_messages_shared_examples.rb, which would otherwise fail once the schedule is enforced.

Behaviour change on GitLab.com. targeted_messages_admin_ui has been enabled on gprd since 29 July 2025 (#528820). Once this deploys, any message whose window has closed stops showing, and a message scheduled for later shows only once it starts. Someone with production access should check for live rows outside their window before this merges.

References

Screenshots or screen recordings

The only visible change is the "Namespace IDs are limited to a maximum of 50,000." hint under the CSV upload field, which now renders.

How to set up and validate locally

  1. Simulate GitLab.com and enable the flag in a Rails console:

    Feature.enable(:targeted_messages_admin_ui)
  2. In Admin > Targeted messages, create a message for a group you own, starting 10 minutes from now.

  3. Open the group's Settings > Billing page. Before this change the banner shows straight away. After it, the banner appears only once the start time passes.

  4. After the message starts, edit it and move the end time. Before this change the save fails with "Starts at cannot be in the past". After it, the save succeeds.

Tests

Run locally in Caproni:

  • RSpec: every targeted message spec passes except 3 GET renders in ee/spec/requests/admin/targeted_messages_controller_spec.rb, which fail in my local test environment because the icon sprite isn't in the asset manifest. The untouched spec/requests/admin/broadcast_messages_controller_spec.rb fails the same 3 ways there.
  • I couldn't run ee/spec/features/groups/billing_spec.rb locally because Gitaly won't start from this checkout, so CI has to confirm the shared example fix.
  • Jest: 42 of 42 pass.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Sam Wiskow

Merge request reports

Loading
Loading