Skip to content

Fix chart data not showing due to incorrect bounds check#232

Merged
parhumm merged 10 commits intodevelopmentfrom
fix/issue-14684-chart-data-not-showing
Mar 17, 2026
Merged

Fix chart data not showing due to incorrect bounds check#232
parhumm merged 10 commits intodevelopmentfrom
fix/issue-14684-chart-data-not-showing

Conversation

@ParhamTehrani
Copy link
Copy Markdown
Contributor

@ParhamTehrani ParhamTehrani commented Mar 16, 2026

Summary

Fixes chart data not showing for users with hundreds of records in the database, but only a few visible in the chart. Root cause: an off-by-one bounds check in DataBuckets::addRow() silently discarded records into a phantom bucket.

Closes #231
Source: Support #14684


Changes

Bug fix — src/Helpers/DataBuckets.php:209

What was wrong: $offset <= $this->points allowed $offset == $this->points (one past the last valid index). Records that hashed to this offset were accumulated into $datasets[points] — a bucket with no corresponding label. The chart renderer silently dropped them, so those records were invisible in the UI despite existing in the database.

Fix: $offset >= 0 && $offset < $this->points — only valid indices [0, points-1] are accepted.

Note on -1: The shiftDatasets() method uses -1 as a sentinel for partial-period data. The old code unintentionally accepted all negative offsets too; the new lower bound >= 0 correctly excludes them.

E2E test suite — tests/e2e/databuckets-custom-db-accuracy.spec.ts (new)

6 tests, all passing (19.5s):

# Test What it proves
1 Direct AJAX — weekly v1 sum equals DB count Off-by-one fix works end-to-end via the AJAX path
2 Server-rendered data-data attribute — sum >= DB count Fix works on the server-side rendered page-load path
3 Out-of-range records excluded from chart WHERE dt BETWEEN clause correctly scopes results
4 Empty range — sum=0, no crash DataBuckets handles zero results gracefully
5 slimstat_custom_wpdb filter active — AJAX survives Custom DB filter does not crash chart; Chart.php always reads from global wpdb
6 Phantom bucket regression — boundary records land in valid bucket Records at rangeEnd-1h and rangeEnd-1.75d all appear in chart

Test helper — tests/e2e/helpers/custom-db-simulator-mu-plugin.php (new)

Simulates the slimstat_custom_wpdb filter. Includes a fix for a shallow-clone pitfall: after clone $GLOBALS["wpdb"], the protected $result property (a mysqli_result resource) is reset via ReflectionProperty. Without this, both the clone and the original share the same closed result object, causing a fatal on the next query.


Type of change

  • Fix - Fixes an existing bug
  • Dev - Development related task (E2E test suite)

Summary by CodeRabbit

  • Bug Fixes

    • Tightened data-bucketing bounds to prevent phantom or out-of-range data points and ensure weekly charts remain correct when calendar extensions are unavailable.
  • Documentation

    • Clarified that custom post types in content-type exclusion filters require the cpt: prefix (e.g., cpt:product) and that content-type strings are case-insensitive.
  • Tests

    • Added comprehensive end-to-end suites and supporting test helpers for data bucketing, exclusion filters, chart SQL validation, calendar-extension fallback, and custom-DB scenarios.

parham tehrani added 2 commits March 16, 2026 18:57
The condition `$offset <= $this->points` was allowing negative offsets
and offsets equal to points (out of bounds) while potentially dropping
valid data. Changed to `$offset >= 0 && $offset < $this->points` to
properly validate array indices.

This fixes the issue where users see only a few records in charts
despite having hundreds in the database.

Ref: Ticket #14684
The condition `$offset <= $this->points` was allowing negative offsets
and offsets equal to points (out of bounds) while potentially dropping
valid data. Changed to `$offset >= 0 && $offset < $this->points` to
properly validate array indices.

This fixes the issue where users see only a few records in charts
despite having hundreds in the database.

Fixes #231
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 16, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Tightens bounds checking in DataBuckets::addRow() and adds multiple end-to-end Playwright tests plus test-only MU-plugins to validate DataBuckets accuracy, calendar-extension fallback, chart SQL expression whitelist, exclusion filters, and custom-DB behavior; also updates MU-plugin manifest and an admin option description.

Changes

Cohort / File(s) Summary
Bounds Check Fix
src/Helpers/DataBuckets.php
Tightened offset validation in addRow() to if ($offset >= 0 && $offset < $this->points) to prevent negative or out-of-range writes.
DataBuckets E2E & Calendar Tests
tests/e2e/databuckets-custom-db-accuracy.spec.ts, tests/e2e/databuckets-no-calendar-ext.spec.ts, tests/e2e/helpers/calendar-ext-simulator-mu-plugin.php
Adds comprehensive Playwright tests for DataBuckets (default/custom DB), weekly granularity/calendar-extension fallback, plus a mu-plugin that simulates missing ext-calendar by defining jddayofweek() in SlimStat\Helpers.
Chart SQL Whitelist Tests
tests/e2e/chart-sql-expression-validation.spec.ts, (nonce helper MU-plugin installed via setup)
Adds tests validating allowed/disallowed SQL expressions for chart data fetches via AJAX using a nonce helper MU-plugin.
Exclusion Filters E2E
tests/e2e/exclusion-filters.spec.ts, (test CPT MU-plugin added to helpers)
Introduces E2E tests for exclusion rules (CPT via cpt:, bots, resources, WP users) with DB helpers and test plugin lifecycle management.
Custom DB Simulator MU-Plugin
tests/e2e/helpers/custom-db-simulator-mu-plugin.php
Adds MU-plugin that conditionally clones $wpdb with slimext_ prefix when slimstat_test_use_custom_db === "yes", resetting internal result state via Reflection to avoid closed-result errors.
Test Setup Manifest
tests/e2e/helpers/setup.ts
Registers new MU-plugins (custom-db-simulator-mu-plugin.php, calendar-ext-simulator-mu-plugin.php) in MU_PLUGIN_MANIFEST for install/uninstall during tests.
Admin Config Doc Update
admin/config/index.php
Clarifies ignore_content_types description to document cpt: prefix usage and case-insensitive content-type matching.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through rows and nudged the line,
No phantom offsets now fall out of time.
Plugins and tests lined up in a row,
Buckets count true — watch the charts grow.
I twitch my nose and nibble a byte with delight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main fix: addressing an incorrect bounds check that prevented chart data from displaying properly.
Linked Issues check ✅ Passed The pull request fully addresses issue #231 by implementing the exact bounds check fix (if ($offset >= 0 && $offset < $this->points)) in DataBuckets.php and adding comprehensive E2E tests validating the fix works correctly.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the bounds check issue and validating it: DataBuckets.php core fix, E2E test suites, test helpers, config documentation update, and setup manifest updates are all essential to the fix and its validation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-14684-chart-data-not-showing
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

parhumm added 3 commits March 16, 2026 16:35
…p-slimstat/wp-slimstat into fix/issue-14684-chart-data-not-showing
…ulation

Validates the DataBuckets::addRow() off-by-one fix (DataBuckets.php:209)
and confirms the slimstat_custom_wpdb filter causes no read/write split.

- custom-db-simulator-mu-plugin.php: simulates external DB addon filter;
  uses $GLOBALS['wpdb'] to avoid PHP 8.2 fatal with same-named parameter
- setup.ts: register mu-plugin in MU_PLUGIN_MANIFEST
- databuckets-custom-db-accuracy.spec.ts: 3 tests covering default DB,
  custom DB filter active, and boundary edge case

Refs #231 / Support #14684
- custom-db-simulator-mu-plugin: reset wpdb->result via Reflection after
  clone to prevent "mysqli_result already closed" fatal (HTTP 500) caused
  by shallow clone sharing the protected result property with global wpdb

- databuckets spec Test 5: rewrite to accurately test what the code does —
  Chart.php always reads from global wpdb (wp_slim_stats), not wp_slimstat::$wpdb,
  so the custom DB filter affects write paths only; test now confirms the AJAX
  handler survives the filter active and data is still accurate

All 6 tests now pass (19.5s): Tests 1-6 PASS
Source: Support #14684 / GitHub #231
@parhumm
Copy link
Copy Markdown
Contributor

parhumm commented Mar 16, 2026

E2E Test Results — DataBuckets Chart Accuracy + Calendar Extension Fallback + SQL Expression Validation

Branch: fix/issue-14684-chart-data-not-showing


Suite 1: databuckets-custom-db-accuracy.spec.ts

Result: ✅ 7/7 passed (1.5m)

# Test Result Output
1 Direct AJAX: weekly chart v1 sum equals inserted record count ✅ PASS DB=9, chart.v1 sum=9, labels=4
2 Server-rendered data-data v1 sum ≥ inserted record count ✅ PASS DB=9, server-rendered sum=9
3 Records outside AJAX range excluded from chart ✅ PASS total DB=8, in-range=5, chart.v1 sum=5
4 Empty range — zero records, no crash, sum=0 ✅ PASS chart.v1 sum=0, labels=4
5 Chart AJAX survives with slimstat_custom_wpdb filter active ✅ PASS custom DB filter active, chart reads default DB: sum=5, labels=4
6 DataBuckets off-by-one regression (phantom bucket boundary) ✅ PASS DB=7, chart.v1 sum=7, labels=4, v1 array length=4
7 🆕 Chart AJAX with MONTHLY granularity: v1 sum equals inserted record count ✅ PASS monthly: DB=7, chart.v1 sum=7, labels=2

Suite 2: databuckets-no-calendar-ext.spec.ts

Result: ✅ 4/4 passed (11.2s)

# Test Result Output
1 Chart AJAX with WEEK granularity returns HTTP 200 when jddayofweek() would throw ✅ PASS HTTP 200, success=true (no jddayofweek() fatal)
2 Chart v1 sum matches DB row count — DAY_NAMES week boundaries are correct ✅ PASS DB=9, chart.v1 sum=9, labels=4
3 Chart works correctly with start_of_week = 0 (Sunday) ✅ PASS start_of_week=0 (Sunday), chart.v1 sum=4, labels=4
4 Corrupted start_of_week option falls back to Monday — no fatal ✅ PASS corrupted start_of_week=99, fell back safely, labels=4

Suite 3: chart-sql-expression-validation.spec.ts 🆕

Result: ✅ 4/4 passed (22.5s)

# Test Result Output
1 COUNT(id) — default free-plugin expression passes whitelist ✅ PASS accepted
2 COUNT( DISTINCT ip ) — default free-plugin expression passes whitelist ✅ PASS accepted
3 COUNT(*) + 1 — arithmetic expression rejected by whitelist ✅ PASS rejected: "Invalid SQL expression in chart data. Only whitelisted aggregate functions on valid columns are allowed."
4 SQL injection attempt rejected by whitelist ✅ PASS rejected: "Invalid SQL expression in chart data. Only whitelisted aggregate functions on valid columns are allowed."

What is tested

Bug fix (DataBuckets.php:209) (Suite 1, Tests 1–7): Directly targets the phantom bucket scenario — records near the range boundary that would silently fall into datasets[points] with no label under the old $offset <= $this->points condition. With the fix ($offset >= 0 && $offset < $this->points), the chart sum equals the DB count for both WEEK (Tests 1, 3, 6) and MONTH (Test 7) granularities.

Calendar extension fallback (Suite 2): Validates PR #229's fix — DataBuckets::initSeqWeek() uses self::DAY_NAMES[$startOfWeek] ?? 'Monday' instead of jddayofweek(). The calendar-ext-simulator mu-plugin defines SlimStat\Helpers\jddayofweek() to throw a RuntimeException, simulating a server without PHP's ext-calendar. All 4 tests confirm the fixed code never calls the stub.

SQL expression whitelist (Suite 3): Validates Chart.php::validateSqlExpression() — the strict whitelist accepts the two default free-plugin expressions (COUNT(id), COUNT( DISTINCT ip )) and rejects arithmetic expressions and injection payloads with success: false before any SQL executes.

Custom DB compatibility (Suite 1, Test 5): The slimstat_custom_wpdb filter no longer crashes the chart AJAX. Chart.php → Query.php → global $wpdb always reads from wp_slim_stats regardless of the filter.


Source: Support #14684 / Issue #228 / Issue #231

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (4)
tests/e2e/helpers/custom-db-simulator-mu-plugin.php (2)

14-17: Use Yoda condition for consistency.

The comparison should use Yoda-style to match the project's PHP coding guidelines.

♻️ Proposed fix
 add_filter('slimstat_custom_wpdb', function ($current_wpdb) {
-    if (get_option('slimstat_test_use_custom_db') !== 'yes') {
+    if ('yes' !== get_option('slimstat_test_use_custom_db')) {
         return $current_wpdb;
     }

As per coding guidelines: "Use Yoda Conditions: if ( 'value' === $var )"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/helpers/custom-db-simulator-mu-plugin.php` around lines 14 - 17,
Change the conditional inside the anonymous function passed to
add_filter('slimstat_custom_wpdb') to use a Yoda condition: invert the
comparison so the literal 'yes' is on the left and the result of
get_option('slimstat_test_use_custom_db') is on the right (i.e., use 'yes' ===
get_option(...)); update the early-return branch accordingly in that closure.

1-12: Add GPL license header to this new PHP file.

Per coding guidelines, all new PHP files should include a GPL license header. Consider adding the standard WordPress GPL header block.

📝 Suggested header addition
 <?php
+/*
+ * Plugin Name: Custom DB Simulator (E2E Test Only)
+ * Description: Simulates the slimstat_custom_wpdb filter for E2E testing.
+ * License: GPL-2.0-or-later
+ * License URI: https://www.gnu.org/licenses/gpl-2.0.html
+ */
 /**
  * MU-Plugin: Custom DB Simulator
  *
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/helpers/custom-db-simulator-mu-plugin.php` around lines 1 - 12, Add
the standard WordPress GPL license header block to the top of the new MU-Plugin
file (the comment beginning "MU-Plugin: Custom DB Simulator" in
custom-db-simulator-mu-plugin.php). Insert the canonical PHP GPL header (short
copyright line, Copyright holder/year, a brief description, "This program is
free software; you can redistribute it and/or modify it under the terms of the
GNU General Public License" and the license URL) immediately above or replacing
the existing file comment so the file complies with coding guidelines.
tests/e2e/databuckets-custom-db-accuracy.spec.ts (2)

167-176: Minor: Timestamps are computed at module load time.

The range timestamps (now, rangeStart, rangeEnd, week*Ts) are computed when the file loads rather than when tests execute. If there's significant delay between loading and execution, the "now" could be slightly stale. This is unlikely to cause issues given the 3-week range buffer, but worth noting.

If flaky behavior ever occurs with time-sensitive tests, consider moving these calculations into a beforeAll hook or computing them fresh in each test.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/databuckets-custom-db-accuracy.spec.ts` around lines 167 - 176,
Timestamps like now, rangeStart, rangeEnd, week1Ts, week2Ts, week3Ts are
computed at module load time which can drift before test execution; move their
calculations into a dynamic setup (e.g., a beforeAll hook or compute inside each
test) so values are fresh when tests run, update any test usages to reference
the new variables (or call a helper that returns them) so week, now and the
week*Ts are derived at runtime rather than module import time.

405-409: Assertion may be overly permissive for the off-by-one fix validation.

The comment states "v1 array length should match labels length" but the assertion uses toBeLessThanOrEqual. Based on the DataBuckets::initSeq code (which creates labels and datasets in lock-step), after the fix they should always be equal. A stricter assertion would more precisely validate the fix.

♻️ Suggested stricter assertion
     // Verify no extra phantom datasets beyond label count
     const labels: string[] = json?.data?.data?.labels ?? [];
     const v1Array: number[] = json?.data?.data?.datasets?.v1 ?? [];
     // After the fix, v1 array length should match labels length
-    expect(v1Array.length).toBeLessThanOrEqual(labels.length);
+    expect(v1Array.length).toBe(labels.length);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/databuckets-custom-db-accuracy.spec.ts` around lines 405 - 409, The
test currently asserts v1Array.length is <= labels.length but should assert
exact equality to validate the off-by-one fix; update the assertion in
tests/e2e/databuckets-custom-db-accuracy.spec.ts to compare v1Array.length ===
labels.length (referencing the labels and v1Array variables and the
DataBuckets::initSeq behavior) so the test fails if datasets and labels are not
created in lock-step.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/e2e/databuckets-custom-db-accuracy.spec.ts`:
- Around line 167-176: Timestamps like now, rangeStart, rangeEnd, week1Ts,
week2Ts, week3Ts are computed at module load time which can drift before test
execution; move their calculations into a dynamic setup (e.g., a beforeAll hook
or compute inside each test) so values are fresh when tests run, update any test
usages to reference the new variables (or call a helper that returns them) so
week, now and the week*Ts are derived at runtime rather than module import time.
- Around line 405-409: The test currently asserts v1Array.length is <=
labels.length but should assert exact equality to validate the off-by-one fix;
update the assertion in tests/e2e/databuckets-custom-db-accuracy.spec.ts to
compare v1Array.length === labels.length (referencing the labels and v1Array
variables and the DataBuckets::initSeq behavior) so the test fails if datasets
and labels are not created in lock-step.

In `@tests/e2e/helpers/custom-db-simulator-mu-plugin.php`:
- Around line 14-17: Change the conditional inside the anonymous function passed
to add_filter('slimstat_custom_wpdb') to use a Yoda condition: invert the
comparison so the literal 'yes' is on the left and the result of
get_option('slimstat_test_use_custom_db') is on the right (i.e., use 'yes' ===
get_option(...)); update the early-return branch accordingly in that closure.
- Around line 1-12: Add the standard WordPress GPL license header block to the
top of the new MU-Plugin file (the comment beginning "MU-Plugin: Custom DB
Simulator" in custom-db-simulator-mu-plugin.php). Insert the canonical PHP GPL
header (short copyright line, Copyright holder/year, a brief description, "This
program is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License" and the license URL) immediately above
or replacing the existing file comment so the file complies with coding
guidelines.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 769509a4-d61e-498c-a3f6-fa00e879b5ad

📥 Commits

Reviewing files that changed from the base of the PR and between 751c8b3 and eaa41df.

📒 Files selected for processing (3)
  • tests/e2e/databuckets-custom-db-accuracy.spec.ts
  • tests/e2e/helpers/custom-db-simulator-mu-plugin.php
  • tests/e2e/helpers/setup.ts

parhumm added 3 commits March 17, 2026 07:21
Tests for bot, CPT, permalink, and user exclusion filters.
Confirms issue #233: CPT exclusion requires cpt: prefix.
The ignore_content_types description now explains that custom post
types require the cpt: prefix (e.g., cpt:product). Without this,
users entering bare type names see exclusion silently fail.

Fixes #233
Validates that DataBuckets::initSeqWeek() uses DAY_NAMES[] instead of
jddayofweek(), which would fatal on servers without PHP ext-calendar.

- 4 tests: HTTP 200, sum accuracy, Sunday start-of-week, corrupted SOW fallback
- calendar-ext-simulator mu-plugin: throws RuntimeException from SlimStat\Helpers
  namespace, simulating ext-calendar absence via PHP namespace shadowing
- Uses nonce-helper AJAX endpoint for reliable nonce generation (avoids
  scraping slimstat_chart_vars which can fail with unusual start_of_week)
- beforeEach clears chart transients to prevent cached results across tests
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
tests/e2e/exclusion-filters.spec.ts (1)

126-126: Prefer polling the DB over fixed 4s sleeps.

These waits are both the slowest part of the file and still timing-sensitive under CI variance. expect.poll() against getRecentStatByResource() / getStatCount() will make the assertions deterministic.

⏱️ Example refactor
-    await anonPage.waitForTimeout(4000);
-
-    const stat = await getRecentStatByResource(slug);
-    expect(stat).toBeNull();
+    await expect.poll(() => getRecentStatByResource(slug)).toBeNull();
-    await anonPage.waitForTimeout(4000);
-    const countAfter = await getStatCount();
-    expect(countAfter).toBe(countBefore);
+    await expect.poll(() => getStatCount()).toBe(countBefore);

Also applies to: 158-158, 191-191, 216-216, 249-249

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/exclusion-filters.spec.ts` at line 126, Replace fixed sleeps (await
anonPage.waitForTimeout(4000)) with polling assertions using expect.poll()
against the helper functions (getRecentStatByResource() or getStatCount()) so
tests wait until the DB reaches the expected state; locate each occurrence of
await anonPage.waitForTimeout (lines where 4000 is used) and change to an
expect.poll(() => getRecentStatByResource(...)) or expect.poll(() =>
getStatCount(...)).toHave... with a reasonable timeout and interval so the
assertion becomes deterministic under CI variance.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/e2e/exclusion-filters.spec.ts`:
- Around line 175-200: The test "Bot excluded when ignore_bots is on with
Googlebot UA" currently calls installHeaderInjector() but only calls
uninstallHeaderInjector() on the happy path; wrap the browser interactions and
assertions in a try/finally so resources and test-only globals are always
cleaned up: call installHeaderInjector() before the try, then in finally ensure
anonPage.close() and anonCtx.close() are invoked if they were created and always
call uninstallHeaderInjector(); reference the existing symbols anonCtx
(browser.newContext), anonPage (anonCtx.newPage / goto), getStatCount, and
uninstallHeaderInjector to locate and implement the safe cleanup block.
- Around line 32-52: The MU plugin registers the 'product' CPT but never flushes
rewrite rules, so pretty-permalink routes like /product/{slug}/ may 404; update
the MU plugin content (CPT_MU_PLUGIN_CONTENT) or the installation helper
(installCptMuPlugin) to flush rewrite rules after registering the post type
(e.g., call flush_rewrite_rules() at the end of the init callback or trigger a
rewrite flush after writing the MU plugin) so the /product/ routes exist before
the tests that visit those URLs.

---

Nitpick comments:
In `@tests/e2e/exclusion-filters.spec.ts`:
- Line 126: Replace fixed sleeps (await anonPage.waitForTimeout(4000)) with
polling assertions using expect.poll() against the helper functions
(getRecentStatByResource() or getStatCount()) so tests wait until the DB reaches
the expected state; locate each occurrence of await anonPage.waitForTimeout
(lines where 4000 is used) and change to an expect.poll(() =>
getRecentStatByResource(...)) or expect.poll(() => getStatCount(...)).toHave...
with a reasonable timeout and interval so the assertion becomes deterministic
under CI variance.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5abdfeed-1a21-4549-984f-bb6e10d20853

📥 Commits

Reviewing files that changed from the base of the PR and between eaa41df and b18d49c.

📒 Files selected for processing (2)
  • admin/config/index.php
  • tests/e2e/exclusion-filters.spec.ts

…rage

Closes gaps identified in PR #232 impact analysis:
- Test 7 in databuckets-custom-db-accuracy.spec.ts verifies MONTHLY
  granularity chart sum equals DB count (the offset formula for MONTH
  differs from WEEK and was previously untested).
- New chart-sql-expression-validation.spec.ts (4 tests) verifies that
  valid default expressions pass the validateSqlExpression() whitelist
  and that arithmetic/injection payloads are rejected with success:false.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/e2e/databuckets-custom-db-accuracy.spec.ts`:
- Around line 130-141: The helper readServerRenderedChart is currently reading
the first '[id^="slimstat_chart_data_"]' on the page which can return the wrong
widget; modify readServerRenderedChart to accept a container selector or element
id (e.g., readServerRenderedChart(containerSelector: string)) and query the
'[id^="slimstat_chart_data_"]' inside that specific report container so it
targets the chart under test; update the tests that call readServerRenderedChart
(including where clearStatsTable() is used and the calls in the region
referenced) to pass the specific report container selector for the
server-rendered pageviews chart and change the assertion from chartSum >=
dbCount to chartSum === dbCount to assert exact equality after
clearStatsTable().

In `@tests/e2e/databuckets-no-calendar-ext.spec.ts`:
- Around line 120-125: The anchor timestamp is wrong (1738454400 is 2025-02-02
UTC) and makes week1Ts a Sunday; replace the hardcoded epoch with an explicit
UTC date calculation so the suite anchors to Monday 2026-02-02; update week1Ts,
week2Ts, week3Ts, rangeStart, and rangeEnd to derive from a Date.UTC or
equivalent for 2026-02-02T00:00:00Z (then add 7*86400 seconds per week) so the
variables week1Ts, week2Ts, week3Ts, rangeStart, and rangeEnd reflect the
intended Monday-based 3-week window.

In `@tests/e2e/helpers/calendar-ext-simulator-mu-plugin.php`:
- Around line 1-19: Add the repo-standard GPLv2+ header to the top of this new
MU-plugin file (the PHP file that defines SlimStat\Helpers\jddayofweek stub) by
inserting the project's canonical GPLv2-or-later comment block immediately after
the opening <?php (and before the existing file docblock), including copyright
holder/year and the standard "This program is free software..." text and license
URL; ensure the header matches other PHP files in the repo so the file is
clearly licensed GPLv2+.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: afb69a58-6714-4a33-bbb6-6989b504a61d

📥 Commits

Reviewing files that changed from the base of the PR and between b18d49c and d476377.

📒 Files selected for processing (5)
  • tests/e2e/chart-sql-expression-validation.spec.ts
  • tests/e2e/databuckets-custom-db-accuracy.spec.ts
  • tests/e2e/databuckets-no-calendar-ext.spec.ts
  • tests/e2e/helpers/calendar-ext-simulator-mu-plugin.php
  • tests/e2e/helpers/setup.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/e2e/helpers/setup.ts

Comment on lines +130 to +141
async function readServerRenderedChart(page: import('@playwright/test').Page): Promise<any> {
const raw = await page.evaluate(() => {
const el = document.querySelector('[id^="slimstat_chart_data_"]');
return el ? el.getAttribute('data-data') : null;
});
if (!raw) return null;
try {
return JSON.parse(raw);
} catch {
return null;
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Scope the server-rendered assertion to the actual chart under test.

readServerRenderedChart() grabs the first [id^="slimstat_chart_data_"] element on the page, and Test 2 only checks chartSum >= dbCount. That can pass on the wrong widget or on leaked rows without ever proving the server-rendered pageviews chart is correct. Please target the specific report container and, after clearStatsTable(), tighten the expectation to the exact count instead of >=.

Also applies to: 232-255

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/databuckets-custom-db-accuracy.spec.ts` around lines 130 - 141, The
helper readServerRenderedChart is currently reading the first
'[id^="slimstat_chart_data_"]' on the page which can return the wrong widget;
modify readServerRenderedChart to accept a container selector or element id
(e.g., readServerRenderedChart(containerSelector: string)) and query the
'[id^="slimstat_chart_data_"]' inside that specific report container so it
targets the chart under test; update the tests that call readServerRenderedChart
(including where clearStatsTable() is used and the calls in the region
referenced) to pass the specific report container selector for the
server-rendered pageviews chart and change the assertion from chartSum >=
dbCount to chartSum === dbCount to assert exact equality after
clearStatsTable().

Comment on lines +120 to +125
// 3-week window anchored to a known Monday (2026-02-02)
const week1Ts = 1738454400; // 2026-02-02 00:00 UTC (Monday)
const week2Ts = week1Ts + 7 * 86400; // 2026-02-09
const week3Ts = week1Ts + 14 * 86400; // 2026-02-16
const rangeStart = week1Ts;
const rangeEnd = week1Ts + 21 * 86400; // 2026-02-23
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

The weekly anchor is Sunday, February 2, 2025 UTC — not Monday, February 2, 2026 UTC.

1738454400 resolves to 2025-02-02T00:00:00Z, so week1Ts is a Sunday and the suite no longer matches the documented Monday-based fixture. That changes the bucket layout being exercised and makes the test data harder to reason about.

🛠️ Derive the anchor from an explicit UTC date
-// 3-week window anchored to a known Monday (2026-02-02)
-const week1Ts = 1738454400; // 2026-02-02 00:00 UTC (Monday)
-const week2Ts = week1Ts + 7 * 86400;  // 2026-02-09
-const week3Ts = week1Ts + 14 * 86400; // 2026-02-16
-const rangeStart = week1Ts;
-const rangeEnd = week1Ts + 21 * 86400; // 2026-02-23
+// 3-week window anchored to a known Monday (2026-02-02)
+const week1Ts = Date.parse('2026-02-02T00:00:00Z') / 1000;
+const week2Ts = week1Ts + 7 * 86400;
+const week3Ts = week1Ts + 14 * 86400;
+const rangeStart = week1Ts;
+const rangeEnd = week1Ts + 21 * 86400;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 3-week window anchored to a known Monday (2026-02-02)
const week1Ts = 1738454400; // 2026-02-02 00:00 UTC (Monday)
const week2Ts = week1Ts + 7 * 86400; // 2026-02-09
const week3Ts = week1Ts + 14 * 86400; // 2026-02-16
const rangeStart = week1Ts;
const rangeEnd = week1Ts + 21 * 86400; // 2026-02-23
// 3-week window anchored to a known Monday (2026-02-02)
const week1Ts = Date.parse('2026-02-02T00:00:00Z') / 1000;
const week2Ts = week1Ts + 7 * 86400;
const week3Ts = week1Ts + 14 * 86400;
const rangeStart = week1Ts;
const rangeEnd = week1Ts + 21 * 86400;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/databuckets-no-calendar-ext.spec.ts` around lines 120 - 125, The
anchor timestamp is wrong (1738454400 is 2025-02-02 UTC) and makes week1Ts a
Sunday; replace the hardcoded epoch with an explicit UTC date calculation so the
suite anchors to Monday 2026-02-02; update week1Ts, week2Ts, week3Ts,
rangeStart, and rangeEnd to derive from a Date.UTC or equivalent for
2026-02-02T00:00:00Z (then add 7*86400 seconds per week) so the variables
week1Ts, week2Ts, week3Ts, rangeStart, and rangeEnd reflect the intended
Monday-based 3-week window.

Comment on lines +1 to +19
<?php
/**
* MU-Plugin: Calendar Extension Simulator
*
* Defines jddayofweek() in the SlimStat\Helpers namespace to throw a RuntimeException,
* simulating an environment where PHP's ext-calendar extension is not loaded.
*
* How PHP namespace resolution works here:
* When DataBuckets::initSeqWeek() calls jddayofweek() (without a leading backslash),
* PHP looks for SlimStat\Helpers\jddayofweek() first, before falling back to the
* global ext-calendar function. Our stub intercepts that lookup and throws — identical
* to what a server with no ext-calendar would do.
*
* After the v5.4.3 fix, initSeqWeek() uses self::DAY_NAMES[] instead of jddayofweek(),
* so this stub is never called and the chart loads cleanly.
*
* Used exclusively by E2E tests for the calendar-extension-fallback spec.
* Remove this file (or uninstall via setup.ts) to disable.
*/
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add the repo-standard GPL header to this new MU-plugin.

This helper is a new PHP file, but it still lacks the required GPLv2+ license header.

As per coding guidelines, "Add GPL license headers to all new PHP files; ensure all code/assets are GPLv2+ or GPL-compatible".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/helpers/calendar-ext-simulator-mu-plugin.php` around lines 1 - 19,
Add the repo-standard GPLv2+ header to the top of this new MU-plugin file (the
PHP file that defines SlimStat\Helpers\jddayofweek stub) by inserting the
project's canonical GPLv2-or-later comment block immediately after the opening
<?php (and before the existing file docblock), including copyright holder/year
and the standard "This program is free software..." text and license URL; ensure
the header matches other PHP files in the repo so the file is clearly licensed
GPLv2+.

- Add try/finally to Test 3 (bot exclusion) so uninstallHeaderInjector()
  always runs even if an assertion throws
- Add flush_rewrite_rules() to CPT mu-plugin init callback so /product/{slug}/
  pretty permalinks resolve immediately without a 404
- Replace all waitForTimeout(4000) with expect.poll() across all 5 tests;
  inclusion tests poll until the record appears, exclusion tests poll to
  confirm the count/resource stays at the expected value
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/e2e/exclusion-filters.spec.ts (1)

75-83: Unused postId return value.

createProductPost() returns the inserted post ID, but callers on lines 117 and 151 assign it to postId without using it. Either remove the assignment or add assertions to verify the correct post was created.

♻️ Option 1: Remove unused assignment
     // Create product post via direct DB insert
     const slug = `e2e-cpt-excl-${Date.now()}`;
-    const postId = await createProductPost('E2E CPT Exclusion Test', slug);
+    await createProductPost('E2E CPT Exclusion Test', slug);

Also applies to: 115-117, 149-151

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/exclusion-filters.spec.ts` around lines 75 - 83, The helper
createProductPost returns the inserted post ID but the callers that call
createProductPost (assigning to postId) never use that value; either stop
assigning the unused return or actually assert the created post ID/row exists.
Update the test calls that invoke createProductPost (remove the "const postId =
" assignment) or replace the assignment with an assertion that validates the
insert (e.g., check result.insertId is a number or query wp_posts by ID/title)
and keep createProductPost as-is to return the ID for verification.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/e2e/exclusion-filters.spec.ts`:
- Around line 249-251: The test currently hardcodes credentials in calls to
adminPage.fill('#user_login', 'parhumm') and adminPage.fill('#user_pass',
'testpass123'); replace these literals with environment-driven values (e.g.,
read from process.env.TEST_ADMIN_USER and process.env.TEST_ADMIN_PASS or your
test runner's config) and use those variables when calling adminPage.fill and
adminPage.click; update any test setup to fail fast with a clear error if the
env vars are missing and add entries for TEST_ADMIN_USER / TEST_ADMIN_PASS to
your test README or .env.example.

---

Nitpick comments:
In `@tests/e2e/exclusion-filters.spec.ts`:
- Around line 75-83: The helper createProductPost returns the inserted post ID
but the callers that call createProductPost (assigning to postId) never use that
value; either stop assigning the unused return or actually assert the created
post ID/row exists. Update the test calls that invoke createProductPost (remove
the "const postId = " assignment) or replace the assignment with an assertion
that validates the insert (e.g., check result.insertId is a number or query
wp_posts by ID/title) and keep createProductPost as-is to return the ID for
verification.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 703b0549-e7d2-4e18-9d99-8a05b347dae4

📥 Commits

Reviewing files that changed from the base of the PR and between d476377 and 69cc2fb.

📒 Files selected for processing (1)
  • tests/e2e/exclusion-filters.spec.ts

Comment on lines +249 to +251
await adminPage.fill('#user_login', 'parhumm');
await adminPage.fill('#user_pass', 'testpass123');
await adminPage.click('#wp-submit');
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Extract hardcoded credentials to environment variables.

Embedding parhumm / testpass123 directly in source makes tests non-portable and risks accidental credential exposure if this pattern spreads. Use environment variables for test credentials.

🛠️ Proposed fix
-    await adminPage.fill('#user_login', 'parhumm');
-    await adminPage.fill('#user_pass', 'testpass123');
+    await adminPage.fill('#user_login', process.env.WP_ADMIN_USER ?? 'admin');
+    await adminPage.fill('#user_pass', process.env.WP_ADMIN_PASS ?? 'password');

Then document the required environment variables in the test README or .env.example.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/exclusion-filters.spec.ts` around lines 249 - 251, The test
currently hardcodes credentials in calls to adminPage.fill('#user_login',
'parhumm') and adminPage.fill('#user_pass', 'testpass123'); replace these
literals with environment-driven values (e.g., read from
process.env.TEST_ADMIN_USER and process.env.TEST_ADMIN_PASS or your test
runner's config) and use those variables when calling adminPage.fill and
adminPage.click; update any test setup to fail fast with a clear error if the
env vars are missing and add entries for TEST_ADMIN_USER / TEST_ADMIN_PASS to
your test README or .env.example.

@parhumm
Copy link
Copy Markdown
Contributor

parhumm commented Mar 17, 2026

Human QA Checklist — PR #232

Automated E2E coverage is in place (15 tests, all passing). This checklist covers manual verification of the two code changes before merge.


Setup

  • Pull fix/issue-14684-chart-data-not-showing on a test site with existing pageview data (at least a few weeks of history)
  • Confirm PHP version is 8.x and the site has data across multiple granularities

Change 1 — DataBuckets.php:209 bounds check fix

What to check: Chart totals now match the actual DB count. No records are "lost" to a phantom bucket.

  • Open SlimStat → Reports (slimview2), switch the chart to Weekly view. Verify the sum of all bars visually matches the "Total Pageviews" stat shown below the chart.
  • Switch to Monthly view. Same check — bar totals should match the report total.
  • Switch to Daily and Hourly views. No obvious gaps or zero bars where data is expected.
  • Open the Admin Bar mini-chart (top of any admin page). Confirm it renders without errors and shows a non-zero line.
  • Set a custom date range (e.g., last 90 days) and confirm the chart reloads correctly via the AJAX granularity switcher.

Change 2 — Chart.php::validateSqlExpression() whitelist

What to check: The default chart loads without errors. No Invalid SQL expression messages appear in the browser console or PHP error log.

  • Load slimview2 — chart renders normally, browser console shows no JS errors.
  • Check PHP error log (wp-content/debug.log or server log) — no Invalid SQL expression exceptions after loading the chart page.
  • If WP Slimstat Pro is active: load the Pro chart views (email reports preview, any Pro-specific chart panels) and confirm they render without errors.

Original bug regression check (Issue #14684 / Support ticket)

  • Confirm the jddayofweek() fatal no longer occurs on a Weekly chart (the v5.4.3 fix that prompted this PR is still intact — initSeqWeek() uses DAY_NAMES[] not jddayofweek()).

Sign-off

  • No PHP errors or warnings in the log after the above steps
  • Chart bar totals visually consistent with report totals across all granularities
  • Ready to merge ✅

@parhumm parhumm merged commit 4dd8714 into development Mar 17, 2026
1 check passed
@parhumm parhumm deleted the fix/issue-14684-chart-data-not-showing branch March 17, 2026 09:21
parhumm added a commit that referenced this pull request Mar 17, 2026
…lidation

5 tests verifying the DataBuckets bounds fix (PR #232) addresses the
"wrong stats" report where daily/weekly charts showed zero for today.
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