Skip to content

Implement “Initiator” scheduled events #11546

@benbowler

Description

@benbowler

Feature Description

  • Register single scheduled event at each frequency
  • Unregister single scheduled event when the feature is disabled
  • Query users at each interval and store each as a custom post

Do not alter or remove anything below. The following sections will be managed by moderators only.

Acceptance criteria

  • The Email_Reporting_Scheduler.php class is defined and integrated into the main Email_Reporting class.
  • When the Proactive User Engagement feature is enabled:
    • Three distinct "Initiator" single scheduled events are registered using wp_schedule_single_event, one for each required frequency: weekly, monthly, and quarterly.
    • Each "Initiator" scheduler is responsible for dealing with only one frequency and will schedule its own next run each time it executes.
  • The weekly "Initiator" scheduler must honor the site's "Week starts on" setting from Settings > General, scheduling its run for the first day of the week based on this setting.
  • When the Proactive User Engagement feature is disabled, or the plugin is uninstalled via Core\Util\Uninstallation, all corresponding scheduled events are unscheduled and removed.
  • When a frequency-level "Initiator" scheduler runs, it must perform the following actions:
    • Schedule the next scheduler for its frequency.
    • Create a unique ID for the batch to track all subsequent steps.
    • Retrieve users whose subscription frequency matches the scheduler's schedule.
    • Create a new CPT post (googlesitekit_email_log) for each retrieved user ID.
      • Store the following metadata in the CPT post: the UUID as the batch ID and the report_frequency in post meta.
      • Calculate and store the report date range object in report_reference_dates in the format { startDate: datestamp, sendDate: datestamp, compareStartDate: datestamp, compareEndDate: datestamp }.
      • Set the post_status of the new CPT post to scheduled.
      • Set the author of the post to be the user ID of the recipient.
    • Spawn the first "Worker" scheduled event for +1 minutes, passing the batch_id.
    • Spawn a "Fallback" scheduled event for +1 hour, passing the frequency.

Implementation Brief

Add Core\Email_Reporting\Frequency_Planner class

  • Implement next_occurrence method:
    • It accepts $frequency, $timestamp and $time_zone arguments
    • Compute the initial timestamp for each frequency:
      • Weekly: determine the target weekday from get_option( 'start_of_week' ) (defaults to 0 = Sunday). Build the next occurrence at the start of that day in site local time (calculate this with wp_timezone()).
      • Monthly/Quarterly: schedule relative to the current date—e.g., monthly on the first of the next month at midnight, quarterly on the first day of the next quarter.
    • Each branch should return the computed timestamp that will be used for scheduling the next event

Add Core\Email_Reporting\Subscribed_Users_Query

  • It should receive instances of Google\Site_Kit\Core\User\Email_Reporting_Settings and Modules and pass them to a class properties.
  • Add for_frequency( $frequency ) method to resolve the subscriber list:
    • Filter WordPress users (admins + shared users) whose stored email-reporting preference matches the frequency.
    • Retrieve via the new user-settings API (Google\Site_Kit\Core\User\Email_Reporting_Settings class), by enumerating user meta keyed by email_reporting_settings frequency and subscribed flag.
    • To build the query, you can reuse the query for Admins - Has_Connected_Admins::query_connected_admins and a meta_query that requires the Email_Reporting_Settings::OPTION meta to contain ['subscribed' => true, 'frequency' => $frequency].
      • The user-setting API stores its data in user meta keyed by $user_options->get_meta_key( Email_Reporting_Settings::OPTION ), so build the query with that meta key. See Debug_Data::get_connected_user_count_field
    • For view only users pull the shared roles from the dashboard sharing option $this->modules->get_module_sharing_settings()->get_all_shared_roles() (see Module_Sharing_Settings::get_all_shared_roles()). Then if not empty - flatten to a unique list, then fetch users with role__in set to that array and the same meta query as above (subscribed and matching frequency).
    • Call a helper like get_subscribed_user_ids( $frequency ) that performs those two queries, merges the resulting ID arrays, and deduplicates them

Introduce Core\Email_Reporting\Email_Reporting_Scheduler as the event scheduler hub.

  • Register it from the feature’s entry point (Email_Reporting::register()) so it hooks into the plugin lifecycle.
  • Define scheduler constants (WEEKLY, MONTHLY, QUARTERLY)
  • Define action names:
    • ACTION_INITIATOR - googlesitekit_email_reporting_initiator
    • ACTION_WORKER - googlesitekit_email_reporting_worker - further implementation will be handled in 11547
    • ACTION_FALLBACK - googlesitekit_email_reporting_fallback - further implementation will be handled in 11548
  • It should receive instance of Frequency_Planner in the constructor and pass it to the class property
  • Add a new method schedule_initiator_events
    • Loop over the frequency array and for each frequency invoke schedule_initiator_once method passing it the $frequency as value
  • Add schedule_initiator_once( $frequency ):
    • Check if there is already an event scheduled using wp_next_scheduled to return early if it returns value
    • Retrieve the next occurrence from $frequency_planner->next_occurrence( $frequency, time(), wp_timezone() )
    • Scheduled the next initiator using wp_schedule_single_event and computed next occurrence. Use self::ACTION_INITIATOR for a the hook and pass frequency in arguments array
  • Add schedule_next_initiator( $frequency, $timestamp )
    • Like in schedule_initiator_once - compute the next_occurrence and schedule the event (no need to use wp_next_scheduled - just go straight to scheduling the event). Pass received $frequency and $timestamp, and use wp_timezone() as last argument
  • Add schedule_worker( $batch_id, $frequency, $timestamp, $delay = MINUTE_IN_SECONDS )
    • Check if worker (self::ACTION_WORKER) has not been scheduled already (! wp_next_scheduled)
      • Return early if it is
      • Otherwise proceed with wp_schedule_single_event and use passed $timestamp + $delay for the timestamp (first) argument, self::ACTION_WORKER for hook, and batch_id, frequency and timestamp as arguments array
  • Add schedule_fallback( $frequency, $delay = HOUR_IN_SECONDS )
    • Like previously, if self::ACTION_FALLBACK is not scheduled, scheduled it for time() + $delay and pass [ 'frequency => $frequency] to the arguments array
  • Implement unschedule_all():
    • Clear every pending initiator, worker, and fallback action. Use wp_unschedule_hook() or iterate over wp_get_scheduled_event() for each action name, mirroring cleanup patterns Uninstallation::clear_scheduled_events() Invoke it when the global enabled option switches off.
  • Add new CRONs to the Uninstallation::SCHEDULED_EVENTS so they are cleared on uninstall.

Add Core\Email_Reporting\Initiator_Task

  • Accept instance of Email_Reporting_Scheduler and Subscribed_Users_Query and store in a class property
  • Add handle_callback_action( $frequency ) method:
    • Schedule next run via Email_Reporting_Scheduler::schedule_next_initiator( $frequency, $time() )
    • Generate a fresh batch UUID (use wp_generate_uuid4()).
    • Return the subscribed user for the frequency by invoking Subscribed_Users_Query::for_frequency( $frequency )
    • Loop over the returned subscribers list (if not empty) and for each user ID, insert a Email_Log::POST_TYPE post (through a helper on Email_Log):
      • post_author = user ID.
      • post_title = Email_Log::META_BATCH_ID (batch UUID),
      • post_status = Email_Log::STATUS_SCHEDULED.
      • meta_input includes Email_Log::META_BATCH_ID (batch UUID), Email_Log::META_REPORT_FREQUENCY (current frequency), Email_Log::META_REPORT_REFERENCE_DATES (date range object for report queries to use) and initializes any other worker fields such as send_attempts to 0.
    • After all posts are created, trigger the follow-up worker using Email_Reporting_Scheduler::schedule_worker() and fallback with Email_Reporting_Scheduler::schedule_fallback()

Update Google\Site_Kit\Core\Email_Reporting\Email_Reporting

  • It should accept instance of Modules in the constructor, and update includes/Plugin.php - pass the $modules to the class instantiation
  • In register():
    • Instantiate FrequencyPlanner, Subscribed_Users_Query, Email_Reporting_Scheduler and Initiator_Task
    • Invoke Email_Reporting_Scheduler::schedule_initiator_events() when feature is enabled Google\Site_Kit\Core\Email_Reporting\Email_Reporting_Settings::is_email_reporting_enabled()
    • Hook into the Email_Reporting_Scheduler::ACTION_INITIATOR and invoke handle_callback_action from Initiator_Task instance.
    • Include feature toggle listeners (e.g. hook into the site-level settings datastore update, or expose a public on_feature_enabled() method invoked when the global option flips on/off - Google\Site_Kit\Core\Email_Reporting\Email_Reporting_Settings).
      • Use the existing on_change setting method, you can see an example here
        $this->get_settings()->on_change(
        function ( $old_value, $new_value ) {
        if (
        is_array( $old_value ) &&
        is_array( $new_value ) &&
        isset( array_diff_assoc( $new_value, $old_value )['propertyID'] )
        ) {
        $this->reset_data_available();
        }
        }
        );
  • Uninstall cleanup via Core\Util\Uninstall::register_uninstall_hook() referencing a new Email_Reporting_Scheduler::unschedule_all() when setting is disabled

Test Coverage

  • Add tests for new classes, some examples to test:
    • When feature is enabled schedules exactly one event per frequency with the expected timestamps (mock start_of_week and ensure weekly lands on that weekday).
    • Disabling the feature removes all initiator events; uninstall hook clears them as well.
    • Running an initiator schedules its next occurrence, spawns worker and fallback events with the correct arguments, and creates CPT posts with the right author, status, and meta.
    • If no subscribers are returned, no CPT posts are inserted but worker/fallback are still scheduled.
    • Subscribed_Users_Query is returning correct users for subscribed frequency

QA Brief

  • Enable proactiveUserEngagement feature flag
  • Go to SK settings > admin settings - verify that Email reports are enabled
  • Use a plugin like WP Crontrol to check scheduled events, in the search field include googlesitekit
  • Verify that there are 3 events of googlesitekit_email_reporting_initiator with different frequency - weekly, monthly and quarterly
Image * Weekly should be scheduled for the first beginning of the week current (if today is Mon) or next Monday, monthly should be scheduled for the first (1st) of the next month, and quarterly for the closest quarter
  • Go to admin settings and disable email reports
  • Verify that no googlesitekit_email_reporting_initiator event is scheduled

Changelog entry

  • Add infrastructure for scheduled events at various intervals.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P0High priorityTeam SIssues for Squad 1Type: EnhancementImprovement of an existing feature

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions