Skip to content

NEUSPRT-571: Add Setting For Relationship End Date Trigger - #7

Open
shahrukh-compuco wants to merge 1 commit into
3.17.1-patchesfrom
neusprt-571-add-interval-for-relationship-end-date-trigger
Open

NEUSPRT-571: Add Setting For Relationship End Date Trigger#7
shahrukh-compuco wants to merge 1 commit into
3.17.1-patchesfrom
neusprt-571-add-interval-for-relationship-end-date-trigger

Conversation

@shahrukh-compuco

Copy link
Copy Markdown
Contributor

Overview

The CiviRules "Relationship end date reached" trigger was checking every relationship that had ever ended, on every cron run (every ~15 minutes). On a large database this generated a huge number of queries and made the scheduled job very slow. This PR adds a configurable time window to the trigger so it only picks up recently ended relationships, and makes each relationship trigger a rule only once instead of every day.

Before

  • The trigger matched all relationships with an end date in the past, with no lower bound, on every cron run.
  • De-duplication only excluded contacts already logged today, so the same relationships were re-evaluated (and could re-fire) every single day, indefinitely.
  • Each evaluation ran against an ever-growing civirule_rule_log table using a non-index-friendly date comparison, so cron runs became progressively slower and consumed significant DB capacity.
  • The trigger had no configuration form.

After

  • When adding/editing a rule with this trigger, a new form asks for a window: "Ended within the last [N] [Day(s)/Week(s)/Month(s)/Year(s)] in the past" (N must be greater than 0).
  • Only relationships whose end date falls inside that window are picked up, so the candidate set stays small and bounded.
  • Each relationship now fires the rule once per end date. If the end date is later pushed out and reached again, it becomes eligible again.
  • Existing rules that have no window configured keep the legacy behaviour unchanged, so nothing changes silently on upgrade.
Screenshot 2026-08-03 at 12 17 32 PM

Technical Details

  • CRM/CivirulesCronTrigger/RelationshipEndDate.php
    • queryForTriggerEntities() now branches: if the trigger has interval + interval_unit params, it runs a windowed query; otherwise it runs the legacy query.
    • Windowed query: end_date >= DATE_SUB(CURDATE(), INTERVAL %2 <UNIT>) bounds the scan, and de-duplication is per relationship via LEFT JOIN civirule_rule_log ON rule_id / entity_table = 'civicrm_relationship' / entity_id = r.id AND log_date >= r.end_date ... WHERE rule_log.id IS NULL.
    • The interval unit is validated against a hardcoded whitelist (days/weeks/months/yearsDAY/WEEK/MONTH/YEAR) before being interpolated into SQL; the interval value is passed as a bound Integer parameter.
    • Added getExtraDataInputUrl(), intervals(), getTriggerDescription() and getHelpText().
    • In the legacy query, DATE(log_date) = DATE(NOW()) was replaced with the equivalent but index-friendly (sargable) log_date >= CURDATE().
  • CRM/CivirulesCronTrigger/Form/RelationshipEndDate.php (new) + templates/CRM/CivirulesCronTrigger/Form/RelationshipEndDate.tpl (new)
    • Trigger parameters form (interval + unit), stored in civirule_rule.trigger_params. Validates the interval as a positive integer greater than 0.
  • xml/Menu/civirules.xml
    • New route civicrm/civirule/form/trigger/relationshipenddate for the form.

Comments

  • Behavioural note for rule configurators: with a window configured, de-duplication changes from "once per contact per day, repeatedly" to "once per relationship per end date". If a contact has multiple ended relationships of the same type in the window, each one fires separately (the legacy query only fired the most recent one).
  • A CiviCRM cache flush is required after deployment so the new menu route is registered.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new CiviRules cron trigger that fires when a relationship's end date is reached, allowing users to target relationships that ended within a specified interval. The feedback focuses on PHP 8 compatibility improvements, specifically recommending defensive checks to prevent warnings when unserializing trigger parameters or accessing potentially non-array properties like triggerParams.

Comment thread CRM/CivirulesCronTrigger/Form/RelationshipEndDate.php
Comment thread CRM/CivirulesCronTrigger/RelationshipEndDate.php
Comment thread CRM/CivirulesCronTrigger/RelationshipEndDate.php

@erawat erawat left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved. Core mechanism is sound and upgrade-safe — verified the de-dup join keys (entity_table/entity_id) are populated by logRule() even on the legacy path, so no double-firing on upgrade; interval binding + unit whitelist are injection-safe.

Non-blocking follow-ups:

  1. Product sign-off needed on the once-per-latest → once-per-every-relationship behavioral change (currently only a footnote).
  2. default: return parent::getHelpText($context) in RelationshipEndDate.php calls a non-existent base method — latent fatal, should return ''.
  3. Trigger help text never renders because the form does not override getHelpText() (cf. MembershipEndDate form).
  4. Consider unit tests around the window boundary and once-per-end-date de-dup.

Whatever is adjusted here should be mirrored on the sibling PR since the diffs are identical.

@shahrukh-compuco
shahrukh-compuco force-pushed the neusprt-571-add-interval-for-relationship-end-date-trigger branch from efef7c8 to 01247d3 Compare August 3, 2026 11:29
@shahrukh-compuco
shahrukh-compuco force-pushed the neusprt-571-add-interval-for-relationship-end-date-trigger branch from 01247d3 to 07bdf81 Compare August 3, 2026 11:30
@shahrukh-compuco

Copy link
Copy Markdown
Contributor Author

Approved. Core mechanism is sound and upgrade-safe — verified the de-dup join keys (entity_table/entity_id) are populated by logRule() even on the legacy path, so no double-firing on upgrade; interval binding + unit whitelist are injection-safe.

Non-blocking follow-ups:

  1. Product sign-off needed on the once-per-latest → once-per-every-relationship behavioral change (currently only a footnote).
  2. default: return parent::getHelpText($context) in RelationshipEndDate.php calls a non-existent base method — latent fatal, should return ''.
  3. Trigger help text never renders because the form does not override getHelpText() (cf. MembershipEndDate form).
  4. Consider unit tests around the window boundary and once-per-end-date de-dup.

Whatever is adjusted here should be mirrored on the sibling PR since the diffs are identical.

@erawat 1 and 3 should be fine imo, 2and 4 have been addressed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants