Skip to content

fix(adminbar): auto-refresh all modal stats and chart every minute (#224)#225

Merged
parhumm merged 8 commits intodevelopmentfrom
fix/adminbar-full-refresh-223-224
Mar 15, 2026
Merged

fix(adminbar): auto-refresh all modal stats and chart every minute (#224)#225
parhumm merged 8 commits intodevelopmentfrom
fix/adminbar-full-refresh-223-224

Conversation

@parhumm
Copy link
Copy Markdown
Contributor

@parhumm parhumm commented Mar 15, 2026

Summary

  • New AJAX endpoint slimstat_get_adminbar_stats returns all admin bar data (online, sessions, views, referrals, chart) in a single request with 60s transient caching
  • All 4 stat cards + chart now auto-refresh every minute (previously only "Online Users" refreshed)
  • Performance: 6 DB queries combined into 2 using conditional aggregates; chart data reuses LiveAnalyticsReport::get_users_chart_data() transient cache — ~1 fresh DB query per minute in steady state
  • [Bug] Admin bar real-time chart data diverges from Live Analytics chart #221 consistency preserved: chart data comes from same LiveAnalyticsReport method used by Live Analytics page

Closes #224
Duplicate of #223 (closed)

Changes

File What
admin/index.php New get_adminbar_stats() AJAX handler, extracted query_online_count() + check_ajax_view_capability() helpers, added element IDs to stat cards, extended wp_localize_script with is_pro + i18n
admin/assets/js/adminbar-realtime.js Full rewrite — updates all stats + chart bars from single AJAX call, exposes window.slimstatUpdateAdminBar()
admin/assets/js/admin.js slimstat:minute_pulse now calls new endpoint, shares update function with adminbar-realtime.js
tests/e2e/adminbar-realtime-refresh.spec.ts 6 E2E tests: AJAX response validation, element IDs, i18n, global functions, consistency, no PHP errors
tests/perf/adminbar-refresh-perf.js k6 load test: smoke (1 VU) + load (10 VUs), SLA p95 < 500ms

Test plan

  • 6/6 new E2E tests pass
  • k6 perf test file ready
  • /simplify code review passed
  • No regressions in existing test suite
  • Manual: open admin bar modal, wait 60s, verify all 4 stats + chart update
  • Manual: check Network tab — only 1 AJAX call per minute

Summary by CodeRabbit

  • New Features

    • Holistic admin bar realtime refresh: full admin bar stats (online, sessions, views, referrals) update together with animated counts.
    • Dynamic stacked chart for Pro users with peak highlighting and tooltips.
    • Improved localization support for rate/compare labels.
  • Bug Fixes / Improvements

    • Smarter polling to avoid redundant requests and coordinate updates.
    • Clearer admin bar update error messaging.
  • Tests

    • Added end-to-end and performance tests covering realtime refresh, chart behavior, localization, and endpoint stability.

parhumm added 4 commits March 15, 2026 14:44
, #224)

Previously only Online Users refreshed. Now Sessions Today, Views Today,
Referrals Today, and the 30-bar chart also update via a single AJAX call
(slimstat_get_adminbar_stats) with 60s transient caching for today stats.
Chart data reuses LiveAnalyticsReport::get_users_chart_data() to preserve
#221 consistency.
- Extract query_online_count() and check_ajax_view_capability() helpers
- Combine 6 today-stats queries into 2 using conditional aggregates
- DRY up stat card update blocks with loop in adminbar-realtime.js
- Expose slimstatAnimateElement globally, remove inline animation in admin.js
- Cache i18n at function entry instead of checking 4 times
6 tests: AJAX endpoint validation, element IDs presence, localized
data structure, global function exposure, server/AJAX consistency,
and no-PHP-errors check.
…224)

Smoke (1 VU, 30s) + load (10 VUs, 1m) scenarios. SLA: p95 < 500ms.
Verifies transient caching keeps response times low under concurrent load.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 15, 2026

Warning

Rate limit exceeded

@parhumm has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 17 minutes and 0 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 74e8988d-8d05-4427-89e9-ad406856cf15

📥 Commits

Reviewing files that changed from the base of the PR and between e4dd033 and bb19f56.

📒 Files selected for processing (3)
  • admin/assets/js/adminbar-realtime.js
  • admin/index.php
  • tests/e2e/adminbar-realtime-refresh.spec.ts
📝 Walkthrough

Walkthrough

Replaces per-element online polling with a unified admin-bar stats flow: new AJAX action slimstat_get_adminbar_stats, server handler returning aggregated stats (online, sessions, views, referrals, optional chart), centralized frontend polling, and exported update/animation globals for DOM updates.

Changes

Cohort / File(s) Summary
Backend AJAX & Admin UI
admin/index.php
Adds get_adminbar_stats(), check_ajax_view_capability(), query_online_count(); maps slimstat_get_adminbar_stats; builds cached payload with online/sessions/views/referrals and optional Pro chart; adds stable adminbar DOM IDs and localizes is_pro/i18n.
Admin Pulse Handler
admin/assets/js/admin.js
Reworks minute pulse gating to use combined condition (`onlineVisitorsElement
Realtime Update Module
admin/assets/js/adminbar-realtime.js
Introduces animateElement(), updateAdminBar(data), fetchAdminBarStats(); exposes window.slimstatUpdateAdminBar and window.slimstatAnimateElement; replaces per-element updates with holistic admin bar refresh including Pro chart, tooltips, and peak highlighting; coordinates polling with admin.js to avoid duplicate fetches.
End-to-End Tests
tests/e2e/adminbar-realtime-refresh.spec.ts, tests/e2e/adminbar-chart-consistency.spec.ts
Adds/updates E2E tests validating AJAX endpoint slimstat_get_adminbar_stats, nonce extraction, DOM IDs, global function exposure, server vs AJAX consistency, and conditional Pro vs Free chart behavior.
Performance Tests
tests/perf/adminbar-refresh-perf.js
Adds k6 performance test simulating admin polling of slimstat_get_adminbar_stats, recording latency and cache metrics, validating response structure, and enforcing SLA thresholds.

Sequence Diagram

sequenceDiagram
    participant Client as Frontend (Client)
    participant AJAX as WP Admin AJAX
    participant Server as get_adminbar_stats Handler
    participant DOM as Admin Bar DOM

    Client->>Client: minute poll timer / slimstat:minute_pulse
    Client->>AJAX: POST /admin-ajax.php action=slimstat_get_adminbar_stats, nonce
    AJAX->>Server: invoke get_adminbar_stats()
    Server->>Server: check_ajax_view_capability(), query_online_count(), gather stats & chart
    Server-->>Client: { success:true, data:{ online, sessions, views, referrals, chart? } }
    Client->>Client: window.slimstatUpdateAdminBar(data)
    Client->>DOM: update counts, cards, chart bars, peaks, tooltips
    Client->>Client: window.slimstatAnimateElement() for value transitions
    DOM-->>Client: admin bar updated
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I hopped to the bar and tapped the screen,

Counts grew tall and charts danced clean,
Sessions, views, referrals all sing,
Polls keep time—realtime is in spring,
Hooray! The admin bar gives me a zing!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.92% 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 accurately and concisely summarizes the main change: implementing auto-refresh of all modal stats (not just Online Users) and the chart every minute, which directly addresses the bug described in issue #224.
Linked Issues check ✅ Passed The PR fully meets the requirements of issue #224 by implementing auto-refresh for all four stat cards (Online Users, Sessions Today, Views Today, Referrals Today) and the bar chart on a ~60-second interval through a new AJAX endpoint and coordinated polling mechanism.
Out of Scope Changes check ✅ Passed All code changes are directly aligned with issue #224's requirements: new AJAX endpoint, admin bar refresh logic, DOM element IDs for targeting, and corresponding tests. No unrelated changes detected.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/adminbar-full-refresh-223-224
📝 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
Copy link
Copy Markdown
Contributor Author

parhumm commented Mar 15, 2026

E2E Test Results — All 6 Passed

Running 6 tests using 1 worker

  ✓  1  AJAX endpoint slimstat_get_adminbar_stats returns valid data (4.5s)
  ✓  2  admin bar modal has all required element IDs for auto-refresh (3.9s)
  ✓  3  SlimStatAdminBar localized data includes i18n and is_pro (3.8s)
  ✓  4  slimstatUpdateAdminBar global function is exposed (3.7s)
  ✓  5  AJAX response is consistent with server-rendered stat values (3.7s)
  ✓  6  no PHP errors on admin page with admin bar (785ms)

  6 passed (23.0s)

Environment: Local by Flywheel, WordPress 6.8, PHP 8.2, Chromium (Playwright), macOS


Why These Tests Are Trustworthy

Each test validates a different layer of the fix. If any one layer breaks, a specific test fails — there's no single point of failure that all 6 share.

Test 1: AJAX endpoint returns valid data

What it proves: The new slimstat_get_adminbar_stats PHP handler exists, responds to authenticated POST requests, and returns a correctly structured JSON payload with real DB data.

Why it's reliable: It authenticates as a real admin user (via Playwright storageState), extracts the WordPress nonce from the live page, then calls admin-ajax.php — the same path the browser JS takes. It validates types (Number.isInteger), ranges (≥ 0), and conditional Pro/Free response shape. A broken SQL query, missing handler registration, or nonce mismatch would fail this test.

Test 2: All required element IDs exist in DOM

What it proves: The 8 HTML element IDs that adminbar-realtime.js targets for DOM updates are actually rendered by add_menu_to_adminbar() in PHP.

Why it's reliable: This is the contract between PHP (producer) and JS (consumer). Before this fix, 6 of these IDs didn't exist — the JS had nothing to update. If someone removes an id= attribute from the PHP template, this test catches it immediately. Uses toBeAttached() (not toBeVisible()) because the admin bar modal may be collapsed.

Test 3: Localized script data has i18n + is_pro

What it proves: wp_localize_script passes all required config to the frontend — ajax_url, security (nonce), is_pro flag, and all 5 i18n translation keys.

Why it's reliable: The JS refresh function uses SlimStatAdminBar.i18n.was_last_day etc. to format comparison text. If a key is missing from wp_localize_script, the JS would show undefined instead of translated text. This test reads the actual window.SlimStatAdminBar object from a real WordPress page load — not a mock.

Test 4: Global update functions are exposed

What it proves: window.slimstatUpdateAdminBar() and window.slimstatAnimateElement() are callable functions on the page.

Why it's reliable: admin.js calls window.slimstatUpdateAdminBar(response.data) on minute pulse. If adminbar-realtime.js fails to load or doesn't expose these globals, the admin-side refresh silently breaks with no visible error. This test ensures the cross-script contract holds.

Test 5: AJAX response matches server-rendered values

What it proves: The AJAX endpoint returns data consistent with what PHP rendered into the HTML on page load. Same SQL, same caching layer — no drift between initial render and subsequent refreshes.

Why it's reliable: Compares server-rendered DOM text against AJAX response within seconds. Also validates that formatted values match the regex ^\d[\d,]*$ (proper number strings, not NaN or empty). This guards against the #221 scenario where admin bar and Live Analytics showed different data.

Test 6: No PHP errors on the admin page

What it proves: The admin dashboard loads with HTTP 200 and no Fatal error, PHP Warning, or Class not found in the HTML.

Why it's reliable: Adding new PHP code (the AJAX handler, extracted helpers, localized script data) can trigger autoload failures, undefined method calls, or syntax errors. This is a smoke test that catches any PHP-level breakage introduced by our changes. It runs against the real WordPress admin — not a stub.


What these tests do NOT cover (manual verification needed)

Gap Why not automated Manual check
Visual animation on stat update Playwright can't reliably assert CSS transitions Watch modal for 60s, verify number animation
Exactly 1 AJAX call per minute Timing-sensitive, flaky in CI Check Network tab for slimstat_get_adminbar_stats calls
Chart bar height changes Requires waiting for real data change Verify chart bars update after minute boundary
Frontend-only polling (non-admin pages) Test environment is admin-only auth Visit frontend as logged-in admin, verify polling

k6 performance test

A separate k6 load test (tests/perf/adminbar-refresh-perf.js) validates the endpoint handles 10 concurrent admin users with p95 < 500ms. Run with: k6 run tests/perf/adminbar-refresh-perf.js

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: 7

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

Inline comments:
In `@admin/assets/js/adminbar-realtime.js`:
- Around line 152-160: Don't short-circuit the adminbar fallback: remove the
early "return" inside the typeof SlimStatAdmin check so the fallback poller
remains active until a real minute pulse is observed; specifically, in
admin/assets/js/adminbar-realtime.js replace the early return in the block that
checks "typeof SlimStatAdmin !== 'undefined'" with logic to start or keep the
fallback (call checkMinutePulse()) unless SlimStatAdmin explicitly exposes and
signals an active countdown/pulse, so the admin bar
(window.slimstatUpdateAdminBar()) still refreshes on pages that load
SlimStatAdmin without its shared countdown.
- Around line 80-83: The current calculation forces a minimum heightPct of 3
which turns count === 0 into a visible bar; update the logic around
chartData/count/maxVal so that when count === 0 you set bar.style.height = "0%"
and only apply Math.max(Math.round((count / maxVal) * 100), 3) for positive
counts (i.e., use count > 0 as the guard), referencing the variables count,
maxVal, heightPct and the assignment to bar.style.height to locate where to
change it.

In `@admin/index.php`:
- Around line 1772-1776: The code uses server-local mktime() to compute
$today_start/$yesterday_start/$yesterday_end which mismatches WP site timezone;
replace that with site-timezone based timestamps using current_time('timestamp')
as the base (e.g. $today_start = strtotime('today', current_time('timestamp'))),
compute yesterday using DAY_IN_SECONDS (e.g. $yesterday_start = $today_start -
DAY_IN_SECONDS; $yesterday_end = $today_start - 1), and keep the existing
$site_host/ $referer_like logic unchanged so both rendering and counts use the
same site timezone boundaries.
- Around line 1766-1816: Transient TTL is fixed at 60s which can leave stale
data until the next minute boundary; change the set_transient call for
$transient_key so TTL is aligned to the next minute boundary (compute seconds
until next minute using current time, e.g., 60 - (time() % 60)) instead of
hardcoding 60, mirroring the alignment logic used by
LiveAnalyticsReport::get_users_chart_data(), and ensure TTL is at least 1 second
before calling set_transient($transient_key, $today_stats, ...).
- Around line 1684-1686: The permission-denied branch uses wp_send_json_error
which returns HTTP 200; change it to return a translated 403 response by passing
a 403 status code and a localized message: replace the current
wp_send_json_error(['message' => 'Insufficient permissions']) call with
wp_send_json_error([...translated message using __() or esc_html__()], 403) and
ensure the message uses the correct text domain (wp-slimstat or
wp-slimstat-pro); keep the capability check using
current_user_can($minimum_capability) and ensure the plugin’s text domain is
loaded earlier so translations are available.

In `@tests/e2e/adminbar-realtime-refresh.spec.ts`:
- Around line 151-179: The test captures serverValues (via getText and IDs
'slimstat-adminbar-online-count' and 'slimstat-adminbar-sessions-count') but
never asserts them against the AJAX response, so DOM/AJAX consistency is
unverified; update the test to parse serverValues.sessions and/or
serverValues.online into numeric counts and assert they match (or are within an
acceptable delta) the AJAX-returned data.sessions.count and data.online.count
from the slimstat_get_adminbar_stats response (use the previously-read nonce via
(window as any).SlimStatAdminBar.security and the response json.data fields) to
enforce the intended consistency check.

In `@tests/perf/adminbar-refresh-perf.js`:
- Around line 66-73: The nonce extraction silently fails and sets loggedIn=true
without validating the value; update the admin page parsing to locate and parse
the SlimStatAdminBar object (e.g., from adminRes.body), extract the "security"
field explicitly, validate the nonce length/format (hex of expected length), and
if extraction/validation fails cause the VU iteration to fail (throw or mark
error) instead of setting loggedIn and skipping the slimstat_get_adminbar_stats
call; change references around nonce, SlimStatAdminBar, adminRes, loggedIn and
the code path that calls slimstat_get_adminbar_stats to rely on this validated
nonce.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9a77f46f-86c6-4634-94a8-195f4f05d1aa

📥 Commits

Reviewing files that changed from the base of the PR and between 354ee40 and ac8b715.

📒 Files selected for processing (5)
  • admin/assets/js/admin.js
  • admin/assets/js/adminbar-realtime.js
  • admin/index.php
  • tests/e2e/adminbar-realtime-refresh.spec.ts
  • tests/perf/adminbar-refresh-perf.js

…d nonce format

- Fix nonce extraction: use `var nonce = 'xxx'` pattern (not `nonce: 'xxx'`)
- Skip data comparison for free users (admin bar shows decorative placeholder data)
- Add Pro-specific test: verify chart uses LiveAnalyticsReport, not placeholder
- Add Free-specific test: verify decorative chart has valid structure
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 (3)
tests/e2e/adminbar-chart-consistency.spec.ts (3)

24-24: Prefer condition-based waits over hardcoded timeouts.

page.waitForTimeout(3000) is repeated in multiple tests (lines 24, 139, 170). This pattern can cause flakiness in CI and unnecessarily slows down test execution. Consider waiting for a specific condition instead.

♻️ Suggested improvement
-    await page.waitForTimeout(3000);
+    // Wait for admin bar chart to be populated
+    await page.waitForSelector('.slimstat-adminbar__chart-bar[data-count]', { state: 'attached' });

Or if you need to ensure the SlimStatAdminBar object is loaded:

await page.waitForFunction(() => typeof (window as any).SlimStatAdminBar !== 'undefined');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/adminbar-chart-consistency.spec.ts` at line 24, Replace the
hardcoded sleep calls using page.waitForTimeout with a condition-based wait to
avoid flakiness: locate the occurrences of page.waitForTimeout(3000) in the
tests (e.g., in adminbar-chart-consistency.spec.ts) and change them to wait for
a specific condition such as the SlimStatAdminBar object being defined by using
page.waitForFunction to check typeof (window as any).SlimStatAdminBar !==
'undefined' (or wait for the specific DOM selector or chart element to be
visible if more appropriate); ensure all repeated occurrences (the three places
noted) are updated to use the condition-based wait.

27-38: Consider extracting Pro detection to a reusable helper.

The Pro detection logic and conditional skip pattern is duplicated across 4 test cases. Extracting this to a helper would improve maintainability.

♻️ Example helper extraction

Add to tests/e2e/helpers/:

// helpers/pro-detection.ts
import { Page, TestInfo } from '@playwright/test';

export async function checkProStatus(page: Page): Promise<boolean> {
  return page.evaluate(() => {
    const bar = (window as any).SlimStatAdminBar;
    return bar?.is_pro === true || bar?.is_pro === '1';
  });
}

export async function skipIfNotPro(page: Page, testInfo: TestInfo): Promise<boolean> {
  const isPro = await checkProStatus(page);
  if (!isPro) {
    testInfo.skip(true, 'Pro not active — test requires Pro features');
  }
  return isPro;
}

Then in tests:

const isPro = await skipIfNotPro(page, testInfo);
if (!isPro) return;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/adminbar-chart-consistency.spec.ts` around lines 27 - 38, Duplicate
Pro-detection logic in the test should be extracted to a reusable helper: create
functions like checkProStatus(page: Page) that evaluates
window.SlimStatAdminBar?.is_pro and skipIfNotPro(page: Page, testInfo: TestInfo)
that calls checkProStatus and calls testInfo.skip(...) when not pro; then
replace the inline page.evaluate block and test.skip usage in
adminbar-chart-consistency.spec.ts (and the three other tests) with a call to
skipIfNotPro(page, testInfo) and return early if it reports not-pro. Ensure
helper names match checkProStatus and skipIfNotPro so callers are easy to find
and import.

159-163: Document the source of the placeholder array.

This hardcoded array must match the placeholder data defined in the PHP backend. Consider adding a comment indicating where the source of truth is located to help maintainers keep them in sync.

+    // Source: admin/view/adminbar.php or wherever the placeholder is defined
+    // Keep in sync with the decorative chart data for non-Pro users
     const fakeData = [3, 5, 4, 7, 6, 8, 5, 9, 7, 6, 8, 10, 7, 5, 6, 8, 9, 7, 6, 5, 8, 10, 9, 7, 6, 8, 5, 7, 6, 8];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/adminbar-chart-consistency.spec.ts` around lines 159 - 163, The
hardcoded fakeData array in the test (variable fakeData compared against
adminBarData) is a mirror of the PHP backend placeholder; add a one-line comment
immediately above the fakeData declaration that cites the exact source of truth
in the backend (the PHP constant/function/class name that defines the
placeholder array and the file or class where it lives) and note that the test
must be updated if that backend placeholder changes so maintainers know to keep
them in sync.
🤖 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/adminbar-chart-consistency.spec.ts`:
- Line 24: Replace the hardcoded sleep calls using page.waitForTimeout with a
condition-based wait to avoid flakiness: locate the occurrences of
page.waitForTimeout(3000) in the tests (e.g., in
adminbar-chart-consistency.spec.ts) and change them to wait for a specific
condition such as the SlimStatAdminBar object being defined by using
page.waitForFunction to check typeof (window as any).SlimStatAdminBar !==
'undefined' (or wait for the specific DOM selector or chart element to be
visible if more appropriate); ensure all repeated occurrences (the three places
noted) are updated to use the condition-based wait.
- Around line 27-38: Duplicate Pro-detection logic in the test should be
extracted to a reusable helper: create functions like checkProStatus(page: Page)
that evaluates window.SlimStatAdminBar?.is_pro and skipIfNotPro(page: Page,
testInfo: TestInfo) that calls checkProStatus and calls testInfo.skip(...) when
not pro; then replace the inline page.evaluate block and test.skip usage in
adminbar-chart-consistency.spec.ts (and the three other tests) with a call to
skipIfNotPro(page, testInfo) and return early if it reports not-pro. Ensure
helper names match checkProStatus and skipIfNotPro so callers are easy to find
and import.
- Around line 159-163: The hardcoded fakeData array in the test (variable
fakeData compared against adminBarData) is a mirror of the PHP backend
placeholder; add a one-line comment immediately above the fakeData declaration
that cites the exact source of truth in the backend (the PHP
constant/function/class name that defines the placeholder array and the file or
class where it lives) and note that the test must be updated if that backend
placeholder changes so maintainers know to keep them in sync.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 499f5c7f-a226-43c1-b0cb-a6c506e6407d

📥 Commits

Reviewing files that changed from the base of the PR and between ac8b715 and b3e1805.

📒 Files selected for processing (1)
  • tests/e2e/adminbar-chart-consistency.spec.ts

@parhumm
Copy link
Copy Markdown
Contributor Author

parhumm commented Mar 15, 2026

Issue #221 Verification — Admin Bar Chart Consistency

Ran the adminbar-chart-consistency.spec.ts test suite to verify #221 (admin bar chart data diverging from Live Analytics chart) remains fixed after the #224 changes.

Running 5 tests using 1 worker

  ✓  1  admin bar chart data matches Live Analytics AJAX response (5.4s)
  ✓  2  admin bar chart renders without PHP errors after fix (1.3s)
  ✓  3  admin bar chart data values are non-negative integers (894ms)
  ✓  4  admin bar chart uses LiveAnalyticsReport when Pro is active (3.9s)
  -  5  free users see decorative chart with valid structure (skipped — Pro is active)

  1 skipped
  4 passed (17.4s)

What was verified:

  1. Data consistency: Admin bar chart data-count values exactly match the Live Analytics AJAX response (slimstat_get_live_analytics_data) — both pull from LiveAnalyticsReport::get_users_chart_data() with shared 60s transient cache
  2. No PHP errors: Admin page loads clean (HTTP 200, no Fatal/Warning/Class-not-found)
  3. Valid chart structure: All 30 bars have non-negative integer counts, minutes-ago ranges 29→0
  4. Pro path active: Confirmed chart uses real LiveAnalyticsReport data, NOT the decorative placeholder array

Previous issue (now fixed):

The test was previously failing due to a nonce extraction bug in the test itself (regex expected nonce: colon syntax but the free-path PHP template uses var nonce = assignment syntax). Fixed in commit b3e1805a.

parhumm added 2 commits March 15, 2026 15:53
…ght, HTTP status

- Remove early return in adminbar-realtime.js that suppressed polling on admin
  pages without .refresh-timer; now self-polls everywhere, skips if admin.js
  pulse already handled the minute
- Fix zero-count chart bars showing 3% height — use 0% for count=0 (JS + PHP)
- Fix mktime() using server timezone instead of WP site timezone for today/
  yesterday boundaries — use current_time('timestamp') + DAY_IN_SECONDS
- Align transient TTL to next minute boundary instead of fixed 60s
- Return HTTP 403 (not 200) for permission-denied AJAX responses with
  translated error message
- E2E test: actually compare server-rendered DOM values against AJAX response
  (was capturing but not asserting)
- k6 test: fail properly when nonce extraction fails instead of silently
  skipping all iterations
… fix TTL clock

- Remove dead checkMinutePulse() function — was duplicated inline
- Merge pulseHandledMinute into lastTriggerMinute (pulse listener sets it
  directly, eliminating redundant state)
- Fix TTL alignment to use current_time('timestamp') consistently with
  today_start computation
- E2E test: fail explicitly on empty DOM values instead of masking with || 0
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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
admin/index.php (1)

1123-1125: ⚠️ Potential issue | 🟡 Minor

Server-rendered stats use server timezone while AJAX uses WordPress timezone.

The initial render at lines 1123-1125 uses mktime(0, 0, 0) which relies on PHP's server timezone, while the AJAX handler at lines 1772-1774 correctly uses current_time('timestamp') with WordPress timezone. This mismatch can cause brief inconsistencies between the initial render and the first AJAX refresh around midnight in sites where WordPress timezone differs from server timezone.

Consider aligning the initial render to use site-timezone boundaries:

🔧 Suggested fix
-        $today_start = mktime(0, 0, 0);
-        $yesterday_start = $today_start - 86400;
-        $yesterday_end = $today_start - 1;
+        $today_start = strtotime('today', current_time('timestamp'));
+        $yesterday_start = $today_start - DAY_IN_SECONDS;
+        $yesterday_end = $today_start - 1;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@admin/index.php` around lines 1123 - 1125, The initial render uses
mktime(0,0,0) (server timezone) causing a mismatch with the AJAX handler that
uses current_time('timestamp') (WP timezone); replace the mktime-based midnight
with a site-timezone midnight computed from current_time — e.g. set $today_start
= strtotime( date_i18n('Y-m-d', current_time('timestamp')) ); then recompute
$yesterday_start = $today_start - 86400 and $yesterday_end = $today_start - 1 so
$today_start/$yesterday_start/$yesterday_end use the same WP timezone as the
AJAX handler.
🧹 Nitpick comments (1)
admin/assets/js/adminbar-realtime.js (1)

160-177: Potential duplicate AJAX requests at minute boundary despite guard.

The guard mechanism sets pulseHandledMinute when slimstat:minute_pulse fires (lines 161-163), but there's a timing edge case: if the 200ms interval tick at lines 166-177 fires just before the event listener runs in the same JavaScript task queue, both fetchAdminBarStats() calls could be queued simultaneously.

However, since JavaScript is single-threaded and event listeners fire synchronously when the event is dispatched, the slimstat:minute_pulse listener will execute and set pulseHandledMinute before the next interval tick has a chance to check it. The current implementation should work correctly in practice.

Consider adding a small debounce or request-in-flight flag if duplicate requests are observed in production, but this is not blocking.

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

In `@admin/assets/js/adminbar-realtime.js` around lines 160 - 177, The
minute-boundary race can still enqueue duplicate fetches; add a simple in-flight
guard: introduce a boolean (e.g., adminBarStatsInFlight) and check it in the
interval callback (and in any other caller) before calling fetchAdminBarStats,
set it true immediately before starting the AJAX call inside fetchAdminBarStats
(or just before invoking it) and clear it in the success/error/finally handlers
so concurrent ticks or event listeners cannot start a second request while one
is active; reference pulseHandledMinute, slimstat:minute_pulse,
lastTriggerMinute, setInterval and fetchAdminBarStats to locate where to add the
check and the flag.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@admin/index.php`:
- Around line 1123-1125: The initial render uses mktime(0,0,0) (server timezone)
causing a mismatch with the AJAX handler that uses current_time('timestamp') (WP
timezone); replace the mktime-based midnight with a site-timezone midnight
computed from current_time — e.g. set $today_start = strtotime(
date_i18n('Y-m-d', current_time('timestamp')) ); then recompute $yesterday_start
= $today_start - 86400 and $yesterday_end = $today_start - 1 so
$today_start/$yesterday_start/$yesterday_end use the same WP timezone as the
AJAX handler.

---

Nitpick comments:
In `@admin/assets/js/adminbar-realtime.js`:
- Around line 160-177: The minute-boundary race can still enqueue duplicate
fetches; add a simple in-flight guard: introduce a boolean (e.g.,
adminBarStatsInFlight) and check it in the interval callback (and in any other
caller) before calling fetchAdminBarStats, set it true immediately before
starting the AJAX call inside fetchAdminBarStats (or just before invoking it)
and clear it in the success/error/finally handlers so concurrent ticks or event
listeners cannot start a second request while one is active; reference
pulseHandledMinute, slimstat:minute_pulse, lastTriggerMinute, setInterval and
fetchAdminBarStats to locate where to add the check and the flag.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2d940aeb-706b-41d8-bcb0-f64dc83ad27c

📥 Commits

Reviewing files that changed from the base of the PR and between b3e1805 and e4dd033.

📒 Files selected for processing (4)
  • admin/assets/js/adminbar-realtime.js
  • admin/index.php
  • tests/e2e/adminbar-realtime-refresh.spec.ts
  • tests/perf/adminbar-refresh-perf.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/perf/adminbar-refresh-perf.js

…d try/catch for LiveAnalyticsReport

- Replace tooltip.innerHTML with textContent + createElement (XSS defense-in-depth)
- Wrap LiveAnalyticsReport instantiation in try/catch for graceful degradation
@parhumm parhumm merged commit 30dbfc8 into development Mar 15, 2026
1 check passed
@parhumm parhumm deleted the fix/adminbar-full-refresh-223-224 branch March 15, 2026 15:11
@parhumm parhumm mentioned this pull request Mar 15, 2026
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.

1 participant