Add DB size alert threshold with auto-suggestion and archival signal banner#33
Conversation
Adds properf_order_itemmeta_alert_threshold_mb setting under the WooCommerce Settings section. Engineer enters the order_item_meta table size in MB at which the archival readiness alert should fire. Field is optional — leaving it empty disables the alert. Validates that the value is a positive integer and preserves the previous value on invalid input. Cache-busting hooks wired so the metrics snapshot refreshes when the threshold changes.
- Rename properf_order_itemmeta_alert_threshold_mb to properf_order_itemmeta_db_alert_threshold - Add GB/MB dropdown alongside the threshold number input - JS converts GB to MB before form submit so value is always stored in MB - Update hooks, registration, sanitize callback, and renderer accordingly
Reads cached woo metrics snapshot and computes: (current_size - archivable_size) x 2. Shows suggestion notice below field. If no archivable orders exist for the current retention window, shows a message to set manually instead.
Two-condition check in collect_woo_order_metrics: signal fires when order_itemmeta size >= configured threshold AND archivable orders > 0. Propagated to format_for_bigquery and BigQuery load schema as woo_archival_signal_active (BOOLEAN NULLABLE) and woo_alert_threshold_mb (INTEGER NULLABLE).
Snapshot is stored as array('woo' => array(...)) but the renderer was
reading top-level keys. Extract $woo = $snapshot['woo'] first so
total_orders, orders_older_than_threshold, and order_itemmeta_size_mb
are read from the correct path.
Shows DB Archival Signal row in WooCommerce Order Metrics table with three states: threshold not configured (link to settings), archival recommended (warning, red), or DB health OK (green). Threshold MB and signal active flag sourced from woo_metrics collected by the data collector.
Previously the settings page only read the cached transient, so the threshold suggestion never appeared until after a push. Now if no snapshot exists, the settings page triggers collection directly and caches the result — same data, no extra DB hit on subsequent loads.
If the calculated threshold is below 500 MB the site is too small for archival planning. Show an informational message instead of pre-filling a misleading low value.
Removes the DB Archival Signal row from the WooCommerce Order Metrics table. Adds a coloured banner between the push button and Summary Metrics so the signal is immediately visible without scrolling. Banner is hidden when no threshold is configured.
Previously the banner read alert_threshold_mb from the cached snapshot, causing stale values to show after the option was deleted or changed. Now reads directly from get_option() and re-evaluates the signal on every dashboard load so the banner always reflects current settings.
Dashboard banner now reads alert threshold directly from get_option() instead of cached snapshot — fixes stale cache showing old threshold after option is deleted. Also removed 'Leave empty to disable' from the settings field description.
- Add delete_option hook for threshold to bust cache when option is deleted via WordPress (not just update/add) - Handle negative healthy size edge case with explicit message instead of silent empty field - Add WC() guard before on-demand metrics collection on settings page - Add MIN_ARCHIVAL_THRESHOLD_MB constant to replace magic number 500 - Fix variable indentation alignment in render_dashboard() - Replace all inline styles on signal banner with CSS classes - Preserve GB unit on page reload — JS auto-selects GB if stored MB value is a round multiple of 1024
- Move flex wrapper and suggestion notice margin to CSS classes - Fix format_for_bigquery() key alignment to match existing lines - Fix JS GB restore to handle fractional GB values (>=1024 MB shows in GB with 2 decimals, step switches to 0.01 for GB input)
- Unit dropdown change handler now converts the input value when switching between MB and GB (instead of leaving the raw number unchanged and corrupting the saved value) - setUnit() updates input.min alongside step so sub-1 GB values (e.g. 0.5 GB = 512 MB) are not blocked by HTML5 validation - format_for_bigquery() array: align all => operators to a single column (widest key woo_orders_older_than_threshold + 1 space)
Spec redesigned — QET threshold replaced with order_item_meta size thresholdThe original spec proposed a configurable QET alert threshold (ms). After reviewing production data from MFM, that approach was dropped in favour of Why QET was dropped as the primary signal:
Why order_item_meta size + orders older than threshold:
Signal condition: How the size threshold is configured: The settings page shows a breakdown above the threshold field — current table size, total orders, orders older than the retention window, and an estimated post-archival size. The threshold field is pre-filled using that estimate with 50% headroom added. For an experienced engineer, the breakdown is a verification step. They read the numbers, judge whether the uniform MB/order assumption holds for that client, and override the value if needed — for instance setting a lower threshold for an earlier warning on a high-traffic site. For an engineer who is not yet familiar with the client, the pre-filled value is the answer. They read the one-line description next to the field, confirm the number looks reasonable, and save. The plugin handles the judgment from that point — the dashboard shows either a green OK badge or an amber "Archival recommended" badge. No need to interpret raw numbers or compare QET values. |
Formula validationMFM:
Sankalptaru:
Resetting the DB size alert thresholdThe threshold is calculated once from the current DB snapshot and stored as a fixed value. It does not recompute automatically — this is intentional so the reference point doesn't shift after archival. There is no UI reset. To clear the threshold (e.g. to re-run the suggestion on a fresh DB state after archival), delete the option directly from the database: DELETE FROM <prefix>_options WHERE option_name = 'properf_order_itemmeta_db_alert_threshold';Sankalptaru example: mysql -u root uat_sankalptaru_web -e "DELETE FROM staru_options WHERE option_name = 'properf_order_itemmeta_db_alert_threshold';"After deleting, reload ProPerf > Settings — the field will be empty and the auto-suggestion will run again from the current snapshot. For sites where the DB is below 500 MB, no suggestion is shown and the threshold must be set manually. |
pokhiii
left a comment
There was a problem hiding this comment.
Substantial feature — closes #27 (archival readiness signal). The mechanics are right: two-condition evaluation, cache-busting on the new setting (add/update/delete), NULLABLE schema fields, sanitize callback with user feedback, 500 MB floor as a thoughtful UX addition. Version bump 1.0.3 → 1.1.0 correct for a new feature.
However, walking issue #27's AC list against the diff surfaces three real gaps and one robustness concern. Details inline; summary of what's covered here:
AC verification against #27
- Field exists in WooCommerce Settings, positive integer, sanitize callback, error on invalid ✓
- 500 MB floor for suggestions ✓ (bonus, not in issue — worth adding to the issue if we want future PRs to preserve it)
- No archivable orders → "set manually" message ✓
-
woo_archival_signal_activeandwoo_alert_threshold_mbpushed to BigQuery ✓ - Dashboard banner with two states, hidden when no threshold ✓
- Banner reflects current threshold state immediately ✓ (via live threshold read + cache-bust hooks)
-
total_order_count(woo_total_orders) pushed to BigQuery on every run — AC #1 of #27. Collected incollect_woo_order_metrics()but never added toformat_for_bigquery()or the schema. See inline note. - Settings page shows auto-suggestion with the derivation — #27's UX example is explicit about showing the four numbers (current size, total orders, archivable orders, post-archival estimate) so the engineer can judge whether the uniform MB/order assumption holds. Current notice just says "Pre-filled based on current DB state: estimated post-archival size × 2." See inline note.
- Signal formula divergence — #27 says
suggested threshold = post-archival estimate × 1.5 (50% headroom). Code uses× 2. See inline note.
WP best-practices audit
Clean:
- No new SQL — pure feature layered on existing collector paths.
- All new output escaped (
esc_html,esc_attr,esc_html_e). - Capability gate + nonce inherited from Settings API.
- Sanitize callback validates positivity and preserves prior value on invalid input (matches the pattern set on
sanitize_threshold_years). add_option_+update_option_+delete_option_hooks all wired for the new option — full coverage.- Schema fields marked NULLABLE on BigQuery side.
Minor:
sprintf( __( '...%s...%s...' ), ... )in the banner strings would be more translator-friendly with positional placeholders (%1$s,%2$s). WPCS-level nit only.- Signal is computed twice — once in
collect_woo_order_metrics()(stored in the snapshot, pushed to BQ) and once inrender_dashboard()(recomputed live). Since the cache is busted on threshold change, the dashboard could just trust$woo_metrics['archival_signal_active']and drop the live re-computation. Non-blocking design nit.
Robustness
One inline note on the JS-based GB→MB conversion — a silent misconfiguration path exists if JS is disabled or fails. Details on that inline.
Positive callouts
- Cache-busting on all three lifecycle events (add / update / delete) — better than the pattern used for the earlier settings, and correct.
- 500 MB floor is a good judgment call; the alternative low-value suggestions would have been misleading.
- PR description is genuinely detailed — before/after per file, files-changed table, AC checklist.
Suggested path forward
- Add
woo_total_ordersto the schema and push. Small. - Expand the suggestion notice to show the derivation (roughly matching #27's UX example).
- Decide
× 1.5vs× 2— update either the issue or the code to match. - The GB→MB conversion is the only non-issue-AC concern that has a real user impact; see inline for one server-side approach.
Once (1)–(3) are addressed and (4) is either fixed or explicitly deferred, this is approve-ready.
This reverts commit 30738c6.
…o no-WC early return
…for itemmeta size; cast to float before number_format in dashboard banner
… instead of recomputing live
…D_ADDITION to BigQuery load job
pokhiii
left a comment
There was a problem hiding this comment.
All four inline items landed cleanly — see per-thread replies for verification.
What stood out this round is the self-review discipline. Ajay ran a full second pass after addressing my comments and caught 11 additional issues, several of which would have been real bugs in production:
ALLOW_FIELD_ADDITIONon the BigQuery load job (class-bigquery-client.php:156) — this closes an outstanding concern I'd flagged all the way back on PR #20 that had been carried as a manual-runbook step ever since. Now BigQuery auto-adds new columns and the load job survives future schema additions. Big improvement.- NULLABLE mode on
woo_order_items_size_mb,woo_order_itemmeta_size_mb,woo_orders_older_than_threshold,woo_query_execution_ms— all four receivenullfrom the no-WC early return, so without NULLABLE the load job would have failed on any non-WC site. Would only have surfaced when someone deployed this to a WC-less install. (float)cast beforenumber_formaton the dashboard banner — prevents PHP 8.1TypeErrorwhen a stale transient hasnullfororder_itemmeta_size_mb.elseif ( $healthy > 0 )correction — was$suggested > 0, which routed very small DBs (whereround($healthy * 2) = 0) to the wrong branch. Same fix also came up on thread 2.- Dashboard reads
archival_signal_activefrom the snapshot instead of recomputing live — closes the "duplicate derivation logic" design nit I flagged in the summary body last time. toFixed(4)for MB→GB display +step="any"for GB mode — prevents lossy round-trips like1500 MB → 1.46 GB → 1495 MB on save. Cheap fix, real bug for anyone editing an existing threshold.null !== $itemmeta_sizeguard instead of a falsy check — DB permission errors returningnullno longer silently coerce to0.0and permanently suppress the archival signal.
That's the kind of pass I'd want done on every non-trivial PR — take a step back after addressing feedback and reread the whole change with fresh eyes. Genuinely good engineering discipline.
Approving.
Closes #27
Closes #28
What changed
Before this PR, there was no way for an engineer to know whether a client's database needed archival without opening the dashboard, reading raw numbers, and making a manual judgment call. This PR adds a configurable size threshold for the
wp_woocommerce_order_itemmetatable, evaluates an archival signal against it on every push, and surfaces the result as a dashboard banner — replacing the manual judgment with a clear automated signal.Settings: DB Size Alert Threshold
Before: No threshold setting existed. Archival readiness was left entirely to the engineer.
After: A
DB Size Alert Thresholdfield is added under WooCommerce Settings with an MB/GB unit dropdown. The value is always stored in MB internally; the unit dropdown is submitted as a named form field and conversion from GB to MB happens server-side in the sanitize callback — JS is display-only.When the field is empty, the settings page collects a live metrics snapshot and auto-suggests a threshold using this formula:
A 500 MB minimum floor suppresses the suggestion for sites too small for archival planning — an informational message is shown instead. If no orders are older than the retention window, the suggestion is skipped and the engineer is prompted to set it manually.
Archival signal evaluation
Before:
order_itemmeta_size_mbandorders_older_than_thresholdwere collected but never compared — no signal was evaluated.After: On every metrics collection, the signal fires when both conditions are true simultaneously:
order_itemmeta_size_mb >= alert_threshold_mborders_older_than_threshold > 0The second condition prevents false alerts when a large table is caused entirely by recent orders that cannot be archived yet. If no threshold is configured, no evaluation runs.
Result is pushed to BigQuery as:
woo_archival_signal_active(BOOLEAN)woo_alert_threshold_mb(INTEGER)Dashboard signal banner
Before: No signal was visible on the dashboard. The engineer had to read raw numbers to make a call.
After: A banner appears above the metrics tables — immediately visible without scrolling. Three states:
The threshold is read live from the database on every dashboard load (not from the cached snapshot), so the banner reflects the current state immediately when the threshold is changed or cleared.
Files changed
class-admin-settings.phpDB Size Alert Thresholdfield, auto-suggestion logic, sanitize callback, cache-busting hooksclass-admin-dashboard.phpclass-data-collector.phpcollect_woo_order_metrics(), three new fields informat_for_bigquery()class-bigquery-client.phpwoo_total_orders,woo_archival_signal_active,woo_alert_threshold_mbclass-live-data.phpadmin.cssproperf-wordpress-adapter.phpAcceptance criteria
DB Size Alert Thresholdfield exists under WooCommerce Settings with MB/GB selectorsize >= thresholdANDorders_older_than_threshold > 0woo_total_orders,woo_archival_signal_active, andwoo_alert_threshold_mbpushed to BigQuery on every runSelf-review fixes
A deeper review pass after the initial implementation caught and fixed 11 additional issues:
class-admin-dashboard.php(float)cast beforenumber_format— PHP 8.1+TypeErrorcrash when stale transient hasnullfororder_itemmeta_size_mbclass-data-collector.phparchival_signal_active: falseandalert_threshold_mb: nullto no-WC early return — prevented undefined-index notices informat_for_bigquery()on non-WC sitesclass-admin-settings.phpMath.round(x*100)/100totoFixed(4)— prevents lossless round-trip drift (e.g. 1500 MB → 1.46 GB → 1495 MB on save)class-admin-settings.phpelseif ($suggested > 0)→elseif ($healthy > 0)— wrong branch fired for very small DB sizes whereround($healthy * 2) = 0class-data-collector.php$itemmeta_size ?check tonull !== $itemmeta_size— DB permission errors returningnullno longer silently set size to0.0and permanently suppress the archival signalclass-admin-settings.phpclass-admin-dashboard.phparchival_signal_activeandalert_threshold_mbfrom the woo metrics snapshot instead of recomputing live — removes duplicate derivation logic and single-sources the signalclass-admin-settings.phpclass-bigquery-client.phpALLOW_FIELD_ADDITIONto load job — without it, any existing table with the old schema would fail on the next pushclass-admin-settings.phpstepchanged from0.01toanyfor GB mode — browser was rejecting form submission whentoFixed(4)produced 4-decimal values like1.4648class-bigquery-client.phpwoo_order_items_size_mb,woo_order_itemmeta_size_mb,woo_orders_older_than_threshold, andwoo_query_execution_msasNULLABLE— all four receivenullfrom the no-WC early return, causing load job failures on non-WC sitesDeploy note
This PR adds three new columns to the BigQuery schema:
woo_total_orders,woo_archival_signal_active, andwoo_alert_threshold_mb. Any existing BigQuery table receiving pushes will fail on the next run after this PR merges unless these columns are added first. The load job now includesALLOW_FIELD_ADDITIONas a safety net, but the DDL must still run first for tables where these columns don't exist yet.Ajay is responsible for adding the following fields to the MFM BigQuery table schema before the next scheduled push. In the BigQuery console, open the table → Edit Schema → New fields → Edit as text, paste the JSON below, and save:
[ {"name": "woo_total_orders", "type": "INTEGER", "mode": "NULLABLE"}, {"name": "woo_archival_signal_active", "type": "BOOLEAN", "mode": "NULLABLE"}, {"name": "woo_alert_threshold_mb", "type": "INTEGER", "mode": "NULLABLE"} ]