Skip to content

feat: improve admin dashboard performance, widgets and customization - #4284

Merged
thorsten merged 3 commits into
mainfrom
feature/admin-dashboard-improvements
May 19, 2026
Merged

feat: improve admin dashboard performance, widgets and customization#4284
thorsten merged 3 commits into
mainfrom
feature/admin-dashboard-improvements

Conversation

@thorsten

@thorsten thorsten commented May 18, 2026

Copy link
Copy Markdown
Owner

Performance & UX:

  • Run dashboard init concurrently via Promise.allSettled
  • Cache remote news/version lookups with stale-on-error fallback
  • Add chart skeleton loaders and theme-aware bar palette

New widgets:

  • Popular searches widget (most popular search terms, last 30 days)
  • Content health widget (orphaned and stale FAQ counts)
  • 7/30/90-day range switcher for the visitor chart

Customization:

  • Per-admin widget layout (reorder + show/hide), persisted in the new faqadmindashboard table
  • Layout GET/POST/reset API endpoints with CSRF protection
  • Edit mode with per-widget move/hide controls

Closes #3721

Summary by CodeRabbit

  • New Features

    • Dashboard customization: enter edit mode to reorder widgets, show/hide per-widget, reset/save layout persisted per admin.
    • New widgets: Content Health (orphaned/stale counters) and Popular Searches with loading placeholders.
    • Visitor charts: date-range selector (7/30/90 days) and improved theme-aware rendering and deterministic colors.
  • Other

    • UI loading skeletons and new English labels for dashboard controls.

Review Change Stack

Performance & UX:
- Run dashboard init concurrently via Promise.allSettled
- Cache remote news/version lookups with stale-on-error fallback
- Add chart skeleton loaders and theme-aware bar palette

New widgets:
- Popular searches widget (most popular search terms, last 30 days)
- Content health widget (orphaned and stale FAQ counts)
- 7/30/90-day range switcher for the visitor chart

Customization:
- Per-admin widget layout (reorder + show/hide), persisted in the
  new faqadmindashboard table
- Layout GET/POST/reset API endpoints with CSRF protection
- Edit mode with per-widget move/hide controls

Closes #3721
@coderabbitai

coderabbitai Bot commented May 18, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b7f2a06a-e98c-491c-8b6a-cb7cf880a12b

📥 Commits

Reviewing files that changed from the base of the PR and between ef4257b and 60f6d90.

📒 Files selected for processing (2)
  • phpmyfaq/admin/assets/src/dashboard.ts
  • phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/DashboardController.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/DashboardController.php

📝 Walkthrough

Walkthrough

Adds per-user dashboard layout persistence with an edit-mode UI and per-widget controls, new content-health and popular-searches endpoints and client renderers, chart range controls and theming, PSR-6 caching for remote data, DB schema/migration updates, services wiring, and comprehensive tests.

Changes

Dashboard Widget Customization and Persistence

Layer / File(s) Summary
Templates and edit-mode styles
phpmyfaq/assets/templates/admin/dashboard.twig, phpmyfaq/admin/assets/scss/layout/_dashboard.scss
Adds toolbar, CSRF token, data-pmf-widget IDs, skeletons, new "Content health" and "Popular searches" widgets; adds shimmer skeleton and edit-mode styles (.pmf-dashboard-editing, .pmf-widget-hidden, .pmf-widget-controls).
Client layout module and tests
phpmyfaq/admin/assets/src/dashboard-layout.ts, phpmyfaq/admin/assets/src/dashboard-layout.test.ts
Introduces handleDashboardLayout() to load/save layout via ./api/dashboard/layout, reorder widgets, inject move/hide controls in edit mode, toggle .pmf-dashboard-editing, and support reset; Vitest covers load, ordering, edit toggle, and save POST.
Index init concurrency
phpmyfaq/admin/assets/src/index.ts, phpmyfaq/admin/assets/src/index.test.ts
Imports new dashboard handlers and runs dashboard tasks concurrently with Promise.allSettled, logging individual rejections. Tests updated to include new handlers.
Chart refactor, ranges and feature fetchers
phpmyfaq/admin/assets/src/dashboard.ts, phpmyfaq/admin/assets/src/dashboard.test.ts
Adds shared theming hook, deterministic palettes, visits fetch parameterized by days with 7/30/90 switcher and stale-response guarding; adds fetchContentHealth() and fetchPopularSearches() with XSS-safe rendering and tests for rendering and range behavior.

Server-side persistence, API and data

Layer / File(s) Summary
PHP persistence and tests
phpmyfaq/src/phpMyFAQ/Administration/DashboardLayout.php, tests/phpMyFAQ/Administration/DashboardLayoutTest.php
New readonly DashboardLayout class persists per-user widget config as JSON in faqadmindashboard with get(), save(), reset(); tests cover no-row/insert/update/delete and invalid JSON handling.
API controller: caching, endpoints, layout routes
phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/DashboardController.php
Adds PSR-6 cache injection and fresh/stale helpers, visits accepts days (clamped), news/versions use cache with stale fallback, and new searches, content-health, and layout (get/save/reset) routes with CSRF validation and sanitizeLayout.
Visit aggregation and content-health queries
phpmyfaq/src/phpMyFAQ/Administration/Session.php, phpmyfaq/src/phpMyFAQ/Administration/Faq.php
getVisitsForDays() provides clamped, inclusive per-day buckets; getContentHealthStatistics() returns orphaned and stale FAQ counts using a safe COUNT helper.

Schema, migrations, services and tests

Layer / File(s) Summary
Migrations, schema and registry
phpmyfaq/src/phpMyFAQ/Setup/Migration/Versions/Migration420Alpha.php, phpmyfaq/src/phpMyFAQ/Setup/Migration/MigrationRegistry.php, phpmyfaq/src/phpMyFAQ/Setup/Installation/DatabaseSchema.php, phpmyfaq/src/services.php
Migration420Alpha extended to create faqadmindashboard (and optionally faqgroup_right_category); Migration420Alpha2 removed and deregistered; DatabaseSchema exposes faqadmindashboard; DI wiring injects phpmyfaq.cache. Tests/schema expectations adjusted to 53 tables.
Translations & test cleanups
phpmyfaq/translations/language_en.php, various tests/*
Adds English translation keys for dashboard UI and performs small non-functional test formatting/refactor updates across multiple test suites.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

"🐰 I hopped through templates and style,
Widgets arranged with just a smile,
Charts that change with range and hue,
Saved layouts snug — all shiny and new,
A carrot-coded fix, hooray for you!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.96% 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 PR title 'feat: improve admin dashboard performance, widgets and customization' accurately summarizes the main changes: performance improvements (caching, concurrent init), new widgets (popular searches, content health, date range switcher), and customization features (drag-and-drop layout, persistence).
Linked Issues check ✅ Passed The PR addresses customization/drag-and-drop requirements from #3721 section 4, and partially implements features from sections 1-3 (popular searches from 2.1, content health parallels section 1.4/2.3, visitor insights). However, online users, newly registered users, top contributors, most commented FAQs, language breakdown, backup date, and comment alerts remain unimplemented.
Out of Scope Changes check ✅ Passed All changes are in-scope: dashboard UI/styling, widget layout persistence, API endpoints, content health/popular searches implementations, theme-aware charts, and supporting database schema and test coverage. No unrelated modifications detected.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/admin-dashboard-improvements

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

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

⚠️ Outside diff range comments (2)
phpmyfaq/src/phpMyFAQ/Administration/Session.php (1)

133-141: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix off-by-one in visit window size.

Current bounds generate days + 1 daily buckets (inclusive loop + full days subtraction).

💡 Suggested fix
-        $startDate = $endDate - ($days * 86_400);
+        $startDate = $endDate - (($days - 1) * 86_400);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@phpmyfaq/src/phpMyFAQ/Administration/Session.php` around lines 133 - 141, The
daily-bucket loop currently produces days+1 entries because startDate is
computed as $endDate - ($days * 86_400) while the loop uses for ($date =
$startDate; $date <= $endDate; $date += 86_400), making the range inclusive; fix
by adjusting the start boundary to $startDate = $endDate - (($days - 1) *
86_400) (respecting the min 1 constraint on $days) or alternatively change the
loop condition to $date < $endDate + 86_400 to make the intent explicit; update
the code around $days, $startDate and the for (...) loop and keep usage of
$this->sessionRepository->getSessionTimestamps unchanged.
phpmyfaq/admin/assets/src/dashboard.ts (1)

185-223: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Prevent stale visits responses from overwriting the latest selected range.

Multiple rapid clicks (7/30/90) can resolve out of order and render stale data last.

Proposed fix (request versioning)
+    let latestVisitsRequest = 0;
     const getData = async (days: number): Promise<void> => {
+      const requestId = ++latestVisitsRequest;
       try {
         const response = await fetch(`./api/dashboard/visits?days=${days}`, {
@@
         if (response.status === 200) {
           const visits: { date: string; number: number }[] = await response.json();
+          if (requestId !== latestVisitsRequest) {
+            return;
+          }
 
           visitorChart.data.labels = [];
           visitorChart.data.datasets[0].data = [];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@phpmyfaq/admin/assets/src/dashboard.ts` around lines 185 - 223, The getData
fetch can return out-of-order and overwrite the chart; implement request
versioning by adding a module-level counter or AbortController and checking it
inside getData: increment a requestId (or create/abort a stored AbortController)
each time the range button click handler triggers before calling getData,
capture the current id inside getData, and after awaiting the fetch/response
verify the id still matches (or that the request wasn't aborted) before mutating
visitorChart.data and calling visitorChart.update; update references to getData,
the rangeGroup click handler, and visitorChart accordingly.
🧹 Nitpick comments (4)
phpmyfaq/admin/assets/src/dashboard-layout.test.ts (1)

51-73: ⚡ Quick win

Add a regression case for unordered config payloads.

This test currently sends config already ordered by position, so it won’t catch ordering regressions. Please add a variant with shuffled entries and assert final DOM order by position.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@phpmyfaq/admin/assets/src/dashboard-layout.test.ts` around lines 51 - 73, The
test currently passes an already-ordered config so add a regression case that
supplies a shuffled config payload to the fetch mock and asserts DOM order is
determined by each item’s position property; specifically, update or add a test
that uses handleDashboardLayout() with a fetch mock returning config entries in
random order (e.g., keys 'inactive-faqs', 'recent-users', 'content-health'
shuffled) and then verify the order of elements selected by
document.querySelectorAll('[data-pmf-widget]') matches the ascending position
values and that visibility (e.g., the 'd-none' class on the element with
data-pmf-widget="inactive-faqs") is still respected.
tests/phpMyFAQ/Administration/DashboardLayoutTest.php (1)

24-31: ⚡ Quick win

Restore global DB table prefix after each test for isolation.

This test mutates global static state and does not restore it, which can make other tests order-dependent.

💡 Suggested fix
 class DashboardLayoutTest extends TestCase
 {
+    private string $previousPrefix;
+
     protected function setUp(): void
     {
         parent::setUp();
-
-        Database::setTablePrefix('');
+        $this->previousPrefix = Database::getTablePrefix();
+        Database::setTablePrefix('');
         ...
     }
+
+    protected function tearDown(): void
+    {
+        Database::setTablePrefix($this->previousPrefix);
+        parent::tearDown();
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/phpMyFAQ/Administration/DashboardLayoutTest.php` around lines 24 - 31,
The test sets global static DB table prefix via Database::setTablePrefix('') and
never restores it; to fix, save the original prefix at start of the test class
(e.g., in setUp store Database::getTablePrefix() into a property like
$this->originalPrefix) and restore it in tearDown by calling
Database::setTablePrefix($this->originalPrefix); add or update the
DashboardLayoutTest::tearDown() method to perform the restore so other tests
remain isolated.
phpmyfaq/admin/assets/src/dashboard.test.ts (1)

418-418: ⚡ Quick win

Add an interaction test for 7/90-day range switching.

Current coverage asserts only the initial days=30 fetch. Add a click-flow test for button[data-pmf-range] to verify endpoint/query updates and active-button state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@phpmyfaq/admin/assets/src/dashboard.test.ts` at line 418, Add an interaction
test after the initial assertion that simulates clicking the range buttons
(select elements with attribute button[data-pmf-range]) to verify the component
updates fetch calls and active state; specifically, use the existing fetchMock
to clear or reset calls, fire a click on the button with data-pmf-range="7",
assert fetchMock was called with './api/dashboard/visits?days=7' and that the
clicked button has the active class/aria state, then repeat for
data-pmf-range="90" asserting './api/dashboard/visits?days=90' and the correct
active-button state; ensure to use the same test helpers and DOM queries already
present in dashboard.test.ts and reset mocks between interactions.
phpmyfaq/admin/assets/src/index.ts (1)

109-119: ⚡ Quick win

Log rejected dashboard tasks from Promise.allSettled.

Right now failures are fully swallowed; a small rejection log would preserve debuggability without reintroducing blocking behavior.

Proposed improvement
-  await Promise.allSettled([
+  const dashboardResults = await Promise.allSettled([
     renderVisitorCharts(),
     renderTopTenCharts(),
     getLatestVersion(),
     handleVerificationModal(),
     fetchRecentNews(),
     fetchContentHealth(),
     fetchPopularSearches(),
     handleDashboardLayout(),
   ]);
+  dashboardResults.forEach((result) => {
+    if (result.status === 'rejected') {
+      console.error('Dashboard init task failed:', result.reason);
+    }
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@phpmyfaq/admin/assets/src/index.ts` around lines 109 - 119, The
Promise.allSettled call that runs renderVisitorCharts, renderTopTenCharts,
getLatestVersion, handleVerificationModal, fetchRecentNews, fetchContentHealth,
fetchPopularSearches, and handleDashboardLayout currently swallows failures;
change it to capture the allSettled results into a variable and iterate over
them to log any results with status === "rejected" (include the corresponding
task name by mapping indexes to the function names above and log the rejection
reason via console.error or the existing logger). Ensure you do not rethrow so
behavior stays non‑blocking, but include clear context in each log entry (task
name and reason).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@phpmyfaq/admin/assets/src/dashboard-layout.ts`:
- Around line 66-74: The loop restores widgets using the config array order but
ignores entry.position, so sort the configuration entries by their numeric
position before iterating: call a stable sort on config (e.g.,
config.sort((a,b)=>a.position - b.position)) then iterate, look up widgets via
widgetsByKey, set widget.dataset.pmfHidden = entry.visible ? 'false' : 'true',
and call grid.appendChild(widget) in that sorted order so stored layouts restore
correctly; ensure you handle missing or undefined position values safely (treat
as Infinity or 0 as appropriate).

In `@phpmyfaq/admin/assets/src/dashboard.ts`:
- Around line 216-223: The callback dereferences rangeGroup without a guaranteed
non-null guard under TypeScript strict mode; fix by capturing rangeGroup into a
local constant before the listener (e.g., const rg = rangeGroup) or at the start
of the handler, then check if (!rg) return; and replace
rangeGroup.querySelectorAll(...) with rg.querySelectorAll(...). Update the
listener setup around rangeGroup, the forEach callback on buttons, and the inner
DOM manipulation so all references use the guarded local (rg) to satisfy strict
null-checking.

In `@phpmyfaq/admin/assets/src/index.test.ts`:
- Around line 12-13: Test currently registers mocks for fetchContentHealth and
fetchPopularSearches but never asserts they were invoked; update the test in
index.test.ts to assert the mocks are called (e.g.,
expect(fetchContentHealth).toHaveBeenCalled()/toHaveBeenCalledTimes(1) and same
for fetchPopularSearches) after the dashboard init runs, and add a mock/spy for
handleDashboardLayout (e.g., vi.fn or vi.spyOn) and assert it was invoked as
well so the concurrent init contract is fully covered; reference the mock
identifiers fetchContentHealth, fetchPopularSearches, and handleDashboardLayout
when adding these assertions.

In `@phpmyfaq/src/phpMyFAQ/Administration/DashboardLayout.php`:
- Around line 80-89: The current save() deletes the old row then inserts the new
one, risking data loss if the insert fails; make the replacement atomic by using
a database transaction around the delete+insert (call beginTransaction, perform
the DELETE and INSERT, then commit, rolling back on any failure) or replace the
two-step logic with a single atomic statement such as INSERT ... ON DUPLICATE
KEY UPDATE / REPLACE INTO targeting the faqadmindashboard table so that either
the new config is written or the operation fails without deleting the existing
row; update the code paths that call $database->query and the save() method in
DashboardLayout.php to use the chosen atomic approach and ensure proper error
handling/rollback.

In `@phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/DashboardController.php`:
- Around line 376-379: The endpoint always returns success after calling
DashboardLayout->reset(...) even when persistence fails; update the controller
to check the outcome of the reset call (or catch exceptions thrown by
DashboardLayout::reset/resetLayout) and return a failure JSON and non-200 status
when it fails. Specifically, call
DashboardLayout->reset($this->currentUser->getUserId()), inspect its
boolean/return value or wrap it in try/catch, and on failure return
$this->json(['success'=>false, 'message' => 'Failed to reset dashboard layout' ,
'error' => $e->getMessage() ?? null], 500) (or appropriate message/status)
instead of always returning success.

In `@tests/phpMyFAQ/Controller/Administration/Api/DashboardControllerTest.php`:
- Around line 461-468: The test testSaveLayoutRejectsInvalidBody currently fails
true invalid-JSON assertion because it omits CSRF and can be rejected for auth;
update the test to include a valid CSRF token in the Request so it isolates body
parsing. Specifically, when creating the Request passed to
DashboardController::saveLayout in testSaveLayoutRejectsInvalidBody(), seed the
same CSRF token/value that createAuthenticatedContainer() expects (e.g., add the
CSRF token to the request attributes/post or headers as the app uses) so the
controller proceeds past CSRF validation and only the invalid JSON body is
tested.

---

Outside diff comments:
In `@phpmyfaq/admin/assets/src/dashboard.ts`:
- Around line 185-223: The getData fetch can return out-of-order and overwrite
the chart; implement request versioning by adding a module-level counter or
AbortController and checking it inside getData: increment a requestId (or
create/abort a stored AbortController) each time the range button click handler
triggers before calling getData, capture the current id inside getData, and
after awaiting the fetch/response verify the id still matches (or that the
request wasn't aborted) before mutating visitorChart.data and calling
visitorChart.update; update references to getData, the rangeGroup click handler,
and visitorChart accordingly.

In `@phpmyfaq/src/phpMyFAQ/Administration/Session.php`:
- Around line 133-141: The daily-bucket loop currently produces days+1 entries
because startDate is computed as $endDate - ($days * 86_400) while the loop uses
for ($date = $startDate; $date <= $endDate; $date += 86_400), making the range
inclusive; fix by adjusting the start boundary to $startDate = $endDate -
(($days - 1) * 86_400) (respecting the min 1 constraint on $days) or
alternatively change the loop condition to $date < $endDate + 86_400 to make the
intent explicit; update the code around $days, $startDate and the for (...) loop
and keep usage of $this->sessionRepository->getSessionTimestamps unchanged.

---

Nitpick comments:
In `@phpmyfaq/admin/assets/src/dashboard-layout.test.ts`:
- Around line 51-73: The test currently passes an already-ordered config so add
a regression case that supplies a shuffled config payload to the fetch mock and
asserts DOM order is determined by each item’s position property; specifically,
update or add a test that uses handleDashboardLayout() with a fetch mock
returning config entries in random order (e.g., keys 'inactive-faqs',
'recent-users', 'content-health' shuffled) and then verify the order of elements
selected by document.querySelectorAll('[data-pmf-widget]') matches the ascending
position values and that visibility (e.g., the 'd-none' class on the element
with data-pmf-widget="inactive-faqs") is still respected.

In `@phpmyfaq/admin/assets/src/dashboard.test.ts`:
- Line 418: Add an interaction test after the initial assertion that simulates
clicking the range buttons (select elements with attribute
button[data-pmf-range]) to verify the component updates fetch calls and active
state; specifically, use the existing fetchMock to clear or reset calls, fire a
click on the button with data-pmf-range="7", assert fetchMock was called with
'./api/dashboard/visits?days=7' and that the clicked button has the active
class/aria state, then repeat for data-pmf-range="90" asserting
'./api/dashboard/visits?days=90' and the correct active-button state; ensure to
use the same test helpers and DOM queries already present in dashboard.test.ts
and reset mocks between interactions.

In `@phpmyfaq/admin/assets/src/index.ts`:
- Around line 109-119: The Promise.allSettled call that runs
renderVisitorCharts, renderTopTenCharts, getLatestVersion,
handleVerificationModal, fetchRecentNews, fetchContentHealth,
fetchPopularSearches, and handleDashboardLayout currently swallows failures;
change it to capture the allSettled results into a variable and iterate over
them to log any results with status === "rejected" (include the corresponding
task name by mapping indexes to the function names above and log the rejection
reason via console.error or the existing logger). Ensure you do not rethrow so
behavior stays non‑blocking, but include clear context in each log entry (task
name and reason).

In `@tests/phpMyFAQ/Administration/DashboardLayoutTest.php`:
- Around line 24-31: The test sets global static DB table prefix via
Database::setTablePrefix('') and never restores it; to fix, save the original
prefix at start of the test class (e.g., in setUp store
Database::getTablePrefix() into a property like $this->originalPrefix) and
restore it in tearDown by calling
Database::setTablePrefix($this->originalPrefix); add or update the
DashboardLayoutTest::tearDown() method to perform the restore so other tests
remain isolated.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8b215e45-89d5-4efe-9e88-d4e036a95017

📥 Commits

Reviewing files that changed from the base of the PR and between 265a851 and 03cb67a.

📒 Files selected for processing (30)
  • phpmyfaq/admin/assets/scss/layout/_dashboard.scss
  • phpmyfaq/admin/assets/src/dashboard-layout.test.ts
  • phpmyfaq/admin/assets/src/dashboard-layout.ts
  • phpmyfaq/admin/assets/src/dashboard.test.ts
  • phpmyfaq/admin/assets/src/dashboard.ts
  • phpmyfaq/admin/assets/src/index.test.ts
  • phpmyfaq/admin/assets/src/index.ts
  • phpmyfaq/assets/templates/admin/dashboard.twig
  • phpmyfaq/src/phpMyFAQ/Administration/DashboardLayout.php
  • phpmyfaq/src/phpMyFAQ/Administration/Faq.php
  • phpmyfaq/src/phpMyFAQ/Administration/Session.php
  • phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/DashboardController.php
  • phpmyfaq/src/phpMyFAQ/Controller/Administration/DashboardController.php
  • phpmyfaq/src/phpMyFAQ/Setup/Installation/DatabaseSchema.php
  • phpmyfaq/src/phpMyFAQ/Setup/Migration/MigrationRegistry.php
  • phpmyfaq/src/phpMyFAQ/Setup/Migration/Versions/Migration420Alpha.php
  • phpmyfaq/src/phpMyFAQ/Setup/Migration/Versions/Migration420Alpha2.php
  • phpmyfaq/src/services.php
  • phpmyfaq/translations/language_en.php
  • tests/phpMyFAQ/Administration/DashboardLayoutTest.php
  • tests/phpMyFAQ/Cache/CacheFactoryTest.php
  • tests/phpMyFAQ/Controller/Administration/Api/DashboardControllerTest.php
  • tests/phpMyFAQ/Controller/Frontend/CategoryControllerTest.php
  • tests/phpMyFAQ/EventListener/ApiRateLimiterListenerTest.php
  • tests/phpMyFAQ/FaqTest.php
  • tests/phpMyFAQ/Permission/GroupCategoryPermissionRepositoryTest.php
  • tests/phpMyFAQ/Session/TokenTest.php
  • tests/phpMyFAQ/Setup/Installation/DatabaseSchemaTest.php
  • tests/phpMyFAQ/Setup/Installation/SchemaInstallerTest.php
  • tests/phpMyFAQ/Setup/Migration/MigrationRegistryTest.php
💤 Files with no reviewable changes (2)
  • phpmyfaq/src/phpMyFAQ/Setup/Migration/Versions/Migration420Alpha2.php
  • phpmyfaq/src/phpMyFAQ/Setup/Migration/MigrationRegistry.php

Comment thread phpmyfaq/admin/assets/src/dashboard-layout.ts Outdated
Comment thread phpmyfaq/admin/assets/src/dashboard.ts Outdated
Comment thread phpmyfaq/admin/assets/src/index.test.ts
Comment thread phpmyfaq/src/phpMyFAQ/Administration/DashboardLayout.php Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
phpmyfaq/admin/assets/src/dashboard.ts (1)

498-527: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clear existing news content before appending new list.

fetchRecentNews() appends a new <ul> without clearing prior content, so repeated calls can duplicate items in the widget.

Suggested fix
-      container.appendChild(list);
+      container.replaceChildren(list);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@phpmyfaq/admin/assets/src/dashboard.ts` around lines 498 - 527, The
fetchRecentNews() implementation appends a new list each call without clearing
prior content, causing duplicate items; before creating/adding the new <ul>
(i.e., before list is appended to container in the block handling response.ok),
clear the container's existing content (use container.innerHTML = '' or remove
all child nodes) so only the latest news list is shown; update the code around
container, list, and container.appendChild to perform this clear operation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/DashboardController.php`:
- Around line 91-100: The cache handling in DashboardController (the block that
reads $cached = $item->get() and returns $cached['payload']) doesn't verify that
payload is an array; update both occurrences (the block around the first snippet
and the similar block at lines 111-114) to check is_array($cached['payload'])
before returning and return null if it's not an array so the method's ?array
return type is preserved; keep the existing checks for fetchedAt and TTL, then
add a final guard like if (!is_array($cached['payload'])) { return null; }
before returning the payload.
- Around line 205-208: The code in DashboardController sets $endDate from
$request->server->get('REQUEST_TIME') which can be 0 when the server param is
missing; change it to use a safe fallback by computing $endDate = (int)
($request->server->get('REQUEST_TIME') ?? time()) (or check empty and use
time()) before calling $this->adminSession->getVisitsForDays($endDate, $days) so
getVisitsForDays always receives a valid timestamp.

---

Outside diff comments:
In `@phpmyfaq/admin/assets/src/dashboard.ts`:
- Around line 498-527: The fetchRecentNews() implementation appends a new list
each call without clearing prior content, causing duplicate items; before
creating/adding the new <ul> (i.e., before list is appended to container in the
block handling response.ok), clear the container's existing content (use
container.innerHTML = '' or remove all child nodes) so only the latest news list
is shown; update the code around container, list, and container.appendChild to
perform this clear operation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 00f69405-346e-4b38-9653-f543676236f8

📥 Commits

Reviewing files that changed from the base of the PR and between 03cb67a and ef4257b.

📒 Files selected for processing (11)
  • phpmyfaq/admin/assets/src/dashboard-layout.test.ts
  • phpmyfaq/admin/assets/src/dashboard-layout.ts
  • phpmyfaq/admin/assets/src/dashboard.test.ts
  • phpmyfaq/admin/assets/src/dashboard.ts
  • phpmyfaq/admin/assets/src/index.test.ts
  • phpmyfaq/admin/assets/src/index.ts
  • phpmyfaq/src/phpMyFAQ/Administration/DashboardLayout.php
  • phpmyfaq/src/phpMyFAQ/Administration/Session.php
  • phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/DashboardController.php
  • tests/phpMyFAQ/Administration/DashboardLayoutTest.php
  • tests/phpMyFAQ/Administration/SessionTest.php
🚧 Files skipped from review as they are similar to previous changes (3)
  • phpmyfaq/admin/assets/src/index.test.ts
  • phpmyfaq/src/phpMyFAQ/Administration/Session.php
  • phpmyfaq/admin/assets/src/index.ts

Comment thread phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/DashboardController.php Outdated
@thorsten
thorsten merged commit 66e3dd7 into main May 19, 2026
11 checks passed
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.

Dashboard Feature Suggestions that could enhance the phpMyFAQ dashboard experience!!

1 participant