PluginProbe
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings / 7.1.1
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings v7.1.1
7.2.1 7.2 7.1.2 7.1.1 7.1 7.0.4 7.0.6 7.0.7 6.3.8 6.3.7 6.3.6 6.3.5 6.3.4 6.3.3 6.3.1 trunk 5.7.3 5.7.5 5.8.1 5.8.2 5.8.3 5.8.4 5.8.6 6.0.4 6.0.5 All 36 releases
mlsimport / includes / mlsimport-task-health.php

mlsimport-task-health.php in MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings 7.1.1, at includes/mlsimport-task-health.php

151 lines 6.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Import Task health badge decider (GitHub issue #200).
4 *
5 * Pure function (no WordPress dependency) so it can be unit tested in
6 * isolation. The execution engine already records every run's state, progress,
7 * heartbeat, and error in the task's mlsimport_import_run_status meta, but the
8 * Import Tasks admin list never showed any of it — a stuck or failed task
9 * looked identical to a healthy one ("Importing 10 out of 100" forever, no
10 * warning). This file decides, from that recorded status plus the sync
11 * watermark, what the Status column badge says for one task.
12 */
13
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit; // Exit if accessed directly
16 }
17
18 // A live worker records activity after every listing; the execution engine
19 // treats a run lock silent for 1800 seconds as dead (STALE_AFTER_SECONDS).
20 // The badge uses the same threshold so "Stuck" and lock takeover agree.
21 if ( ! defined( 'MLSIMPORT_TASK_HEALTH_STUCK_AFTER' ) ) {
22 define( 'MLSIMPORT_TASK_HEALTH_STUCK_AFTER', 1800 );
23 }
24
25 // Auto-sync runs hourly and advances the watermark on every successful run, so
26 // a watermark 6+ hours old means several consecutive failed or skipped syncs —
27 // old enough to be a real problem, tolerant of a temporary hiccup.
28 if ( ! defined( 'MLSIMPORT_TASK_HEALTH_OVERDUE_AFTER' ) ) {
29 define( 'MLSIMPORT_TASK_HEALTH_OVERDUE_AFTER', 21600 );
30 }
31
32 /**
33 * Decide the Status column badge for one Import Task.
34 *
35 * Step by step:
36 * 0. No run status and no watermark: a task waiting for its first manual
37 * import — neutral, not a health problem.
38 * 1. A run the engine marked failed is an error badge carrying the run's own
39 * human-readable error, so the administrator reads WHY in the task list.
40 * 2. A running import whose heartbeat went silent longer than the engine's
41 * own staleness rule (30 minutes) is a dead worker: flag it stuck. This is
42 * the issue's reported scenario — "Importing 10 out of 100" forever.
43 * 3. A running import with a fresh heartbeat is healthy: show its progress.
44 * 4. A cron-enabled task without a watermark cannot hourly-sync at all (the
45 * sync refuses to start without one): warn to run one manual import. A
46 * watermark that fell behind the overdue cutoff means the hourly sync has
47 * been failing or skipped for hours: warn with the last sync time.
48 * 5. Auto-sync disabled: neutral "last import" badge — sync wording would be
49 * false framing and an overdue warning would be permanent noise.
50 * 6. Otherwise: syncing normally.
51 *
52 * @param array<string, mixed> $status The task's recorded run status meta
53 * (state, handled, expected, error, activity_at).
54 * @param string $watermark The task's mlsimport_last_date sync watermark ('Y-m-d\TH:i').
55 * @param bool $cron_enabled Whether hourly auto-sync is on for the task.
56 * @param int $now Current Unix timestamp.
57 * @param string $watermark_stale_before Watermarks older than this ('Y-m-d\TH:i') are overdue.
58 * @return array<string, string> Badge as level ('ok'|'warning'|'error'|'neutral'), label, message.
59 */
60 function mlsimport_task_health( array $status, string $watermark, bool $cron_enabled, int $now, string $watermark_stale_before ): array {
61 $state = (string) ( $status['state'] ?? '' );
62
63 // 0. Never ran at all: no run status and no watermark is a task waiting
64 // for its first manual import, not a health problem.
65 if ( '' === $state && '' === $watermark ) {
66 return array(
67 'level' => 'neutral',
68 'label' => 'Never imported',
69 'message' => 'Run a manual import to activate this task.',
70 );
71 }
72
73 // 1. A failed run: show the engine's own stored error message.
74 if ( 'failed' === $state ) {
75 return array(
76 'level' => 'error',
77 'label' => 'Import failed',
78 'message' => (string) ( $status['error'] ?? '' ),
79 );
80 }
81
82 // 2. A silent 'running' status: the worker heartbeats after every listing,
83 // so a heartbeat older than the engine's 30-minute staleness rule means
84 // the worker died and nothing will update this task again — call it stuck.
85 if ( 'running' === $state && $now - (int) ( $status['activity_at'] ?? 0 ) > MLSIMPORT_TASK_HEALTH_STUCK_AFTER ) {
86 return array(
87 'level' => 'error',
88 'label' => 'Stuck',
89 'message' => sprintf(
90 'Stopped at listing %1$d of %2$d — no worker activity for over 30 minutes.',
91 (int) ( $status['handled'] ?? 0 ),
92 (int) ( $status['expected'] ?? 0 )
93 ),
94 );
95 }
96
97 // 3. A live import: recent activity means it is genuinely progressing —
98 // show where it is.
99 if ( 'running' === $state ) {
100 return array(
101 'level' => 'ok',
102 'label' => 'Importing',
103 'message' => sprintf(
104 '%1$d of %2$d listings imported.',
105 (int) ( $status['handled'] ?? 0 ),
106 (int) ( $status['expected'] ?? 0 )
107 ),
108 );
109 }
110
111 // 4a. Cron on but no watermark at all: the hourly sync refuses to start
112 // without one (tasks imported before manual runs seeded it — GitHub issue
113 // #202 follow-up). Say so plainly instead of printing a blank sync date.
114 if ( $cron_enabled && '' === $watermark ) {
115 return array(
116 'level' => 'warning',
117 'label' => 'Sync not started',
118 'message' => 'The hourly sync has no start point yet — run one manual import.',
119 );
120 }
121
122 // 4b. Auto-sync advances the watermark on every successful hourly run, so a
123 // cron-enabled task whose watermark is older than the overdue cutoff has
124 // not synced successfully for hours. The fixed-width 'Y-m-d\TH:i' format
125 // compares correctly as a plain string, no date parsing needed.
126 if ( $cron_enabled && strcmp( $watermark, $watermark_stale_before ) < 0 ) {
127 return array(
128 'level' => 'warning',
129 'label' => 'Sync overdue',
130 'message' => sprintf( 'Last successful sync: %s.', $watermark ),
131 );
132 }
133
134 // 5. Auto-sync disabled: "Synced"/"Sync overdue" would be false framing for
135 // a task that never syncs — state the last import date neutrally instead.
136 if ( ! $cron_enabled ) {
137 return array(
138 'level' => 'neutral',
139 'label' => 'Auto-sync off',
140 'message' => sprintf( 'Last import: %s.', $watermark ),
141 );
142 }
143
144 // 6. Healthy steady state: syncing normally inside the overdue cutoff.
145 return array(
146 'level' => 'ok',
147 'label' => 'Synced',
148 'message' => sprintf( 'Last successful sync: %s.', $watermark ),
149 );
150 }
151