Conversation
|
Caution Review failedThe pull request is closed. 📝 WalkthroughWalkthroughAdds a Logs feature: new Logs REST controller (file/DB sources) with listing, filtering, pagination, fatal-error detection, and mark-read/unread tracking; registers controller in API; exposes unread counts to settings bootstrap; adds a React Logs screen, nav badge, routing, translations, and comprehensive tests and plan docs. Changes
Sequence DiagramsequenceDiagram
participant User as User (Browser)
participant Frontend as Logs Component
participant API as REST API<br/>wcpos/v1/logs
participant Handler as Handler<br/>(File / DB)
participant DB as Database
participant FS as File System
User->>Frontend: Load Logs Page
Frontend->>API: GET /wcpos/v1/logs?level=&page=
API->>Handler: get_handler_type()
alt File handler
Handler->>FS: read log files
FS-->>Handler: log lines
Handler->>Handler: parse_log_line()
Handler-->>API: entries
else DB handler
Handler->>DB: SELECT rows from woocommerce_log
DB-->>Handler: rows
Handler->>Handler: transform rows → entries
Handler-->>API: entries
end
API-->>Frontend: entries + has_fatal_errors + headers
Frontend->>Frontend: render list, filters, pagination
User->>Frontend: Click "Mark as Read"
Frontend->>API: POST /wcpos/v1/logs/mark-read
API->>DB: update user_meta (last viewed)
DB-->>API: success
API-->>Frontend: success
Frontend->>Frontend: reset unread counts (hook)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
E2E API Test Results35 tests 35 ✅ 3s ⏱️ Results for commit 6eadd2b. ♻️ This comment has been updated with latest results. |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In `@docs/plans/2026-02-11-logs-page-plan.md`:
- Around line 11-13: Remove the machine-specific absolute worktree path after
the "Worktree:" label and replace it with a generic placeholder (e.g.,
"Worktree: /path/to/worktree" or "Worktree: ./worktree") so the plan is
portable; leave the "Test command:" line unchanged.
In `@includes/API/Logs.php`:
- Around line 197-218: get_db_entries currently loads the entire log table into
PHP; add optional $limit and $offset parameters to get_db_entries($level = null,
?int $limit = null, ?int $offset = null) and incorporate them into the SQL as
prepared LIMIT/OFFSET clauses (use $wpdb->prepare with %d placeholders) so the
query returns only the requested page; then change get_unread_counts to avoid
calling get_db_entries for counting and instead run a single prepared COUNT(*)
query on the same $wpdb->prefix . 'woocommerce_log' table with the same source =
'woocommerce-pos' and level severity condition to compute unread counts directly
in SQL.
- Around line 81-106: The pagination variables $per_page and $page must be
clamped to positive integers before computing $total_pages and $offset to
prevent negative/zero values; update the assignment logic in the same block
(where $per_page and $page are set and before $total_pages/$offset calculations)
to cast to int and enforce $per_page = max(1, (int)...) and $page = max(1,
(int)...), optionally also bounding $per_page to a sane maximum, so subsequent
math in array_slice, ceil and offset is always valid.
- Around line 351-377: get_unread_counts() is comparing $last_viewed (ISO 8601
stored via gmdate('c')) to $entry['timestamp'] (MySQL DATETIME from
get_db_entries()), which fails due to different formats; normalize both values
to Unix timestamps before the comparison. In the loop inside
get_unread_counts(), convert $last_viewed and $entry['timestamp'] to integers
using strtotime (or DateTime->getTimestamp) when they are non-numeric (e.g.
$last_viewed_ts = is_numeric($last_viewed) ? (int)$last_viewed :
strtotime($last_viewed); $entry_ts = is_numeric($entry['timestamp']) ?
(int)$entry['timestamp'] : strtotime($entry['timestamp'])), then use those
timestamps for the conditional that skips read entries and for any other
ordering/compare logic; reference get_unread_counts(), LAST_VIEWED_META_KEY,
get_db_entries()/get_file_entries(), $last_viewed, and $entry['timestamp'] when
making the change.
In `@packages/settings/src/screens/logs/use-unread-log-counts.ts`:
- Around line 44-51: markLogsRead currently zeroes the shared counts and calls
emitChange before the POST completes; capture the previous counts (the variable
referenced as counts) into a local variable, perform the optimistic update (set
counts = { error:0, warning:0 } and emitChange), then wrap the apiFetch call in
a try/catch; on failure restore counts = previousCounts, call emitChange to roll
back the UI, and rethrow or handle the error as appropriate. Ensure you
reference the markLogsRead function, the counts variable, emitChange, and
apiFetch so the rollback is applied only on request failure.
🧹 Nitpick comments (4)
includes/Admin/Settings.php (1)
117-117: Consider performance impact of loading unread counts on every settings page load.
Logs::get_unread_counts()instantiates the Logs controller and reads all log entries (from file or database) to calculate counts. If there are many log entries, this could add latency to every settings page load.Consider caching the unread counts in a transient or computing them lazily only when the user navigates to a page that displays the badge.
packages/settings/src/screens/logs/index.tsx (3)
48-58: Type safety improvement for pagination metadata.The
_totalPagesproperty is added dynamically but accessed via type assertion(data as any)?._totalPages. Consider extending the response type to include the pagination metadata for better type safety.Proposed type improvement
interface LogsResponse { entries: LogEntry[]; has_fatal_errors: boolean; fatal_errors_url: string; + _totalPages?: number; } // ... -const totalPages = (data as any)?._totalPages ?? 1; +const totalPages = data?._totalPages ?? 1;
153-169: Pagination button labels should be internationalized."Previous" and "Next" are hardcoded strings. For consistency with the rest of the UI, these should use the translation function.
Proposed fix
<button onClick={() => setPage((p) => Math.max(1, p - 1))} disabled={page <= 1} className="wcpos:px-3 wcpos:py-1 wcpos:rounded wcpos:text-sm wcpos:border wcpos:border-gray-300 disabled:wcpos:opacity-50" > - Previous + {t('common.previous', 'Previous')} </button> // ... <button onClick={() => setPage((p) => Math.min(totalPages, p + 1))} disabled={page >= totalPages} className="wcpos:px-3 wcpos:py-1 wcpos:rounded wcpos:text-sm wcpos:border wcpos:border-gray-300 disabled:wcpos:opacity-50" > - Next + {t('common.next', 'Next')} </button>
60-62: Logs marked as read immediately on mount.
markLogsRead()is called unconditionally when the component mounts. This means even a brief navigation to the Logs page will clear all unread badges, regardless of whether the user actually reviewed the entries.This may be intentional UX, but consider whether marking as read should be deferred (e.g., after a short delay or on scroll) to ensure the user had a chance to view the entries.
| **Worktree:** `/Users/kilbot/Projects/woocommerce-pos/.worktrees/feature-logs-page` | ||
|
|
||
| **Test command:** `npx wp-env run --env-cwd='wp-content/plugins/feature-logs-page' tests-cli -- vendor/bin/phpunit -c .phpunit.xml.dist --verbose` |
There was a problem hiding this comment.
Remove the machine-specific worktree path from the plan.
The absolute path is local to one machine and will stale quickly; replace with a generic placeholder.
🤖 Prompt for AI Agents
In `@docs/plans/2026-02-11-logs-page-plan.md` around lines 11 - 13, Remove the
machine-specific absolute worktree path after the "Worktree:" label and replace
it with a generic placeholder (e.g., "Worktree: /path/to/worktree" or "Worktree:
./worktree") so the plan is portable; leave the "Test command:" line unchanged.
| private function get_db_entries( ?string $level = null ): array { | ||
| global $wpdb; | ||
|
|
||
| $table = $wpdb->prefix . 'woocommerce_log'; | ||
|
|
||
| // Check table exists. | ||
| $table_exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ); | ||
| if ( ! $table_exists ) { | ||
| return array(); | ||
| } | ||
|
|
||
| $where = $wpdb->prepare( 'WHERE source = %s', 'woocommerce-pos' ); | ||
|
|
||
| if ( $level ) { | ||
| $severity = \WC_Log_Levels::get_level_severity( $level ); | ||
| $where .= $wpdb->prepare( ' AND level = %d', $severity ); | ||
| } | ||
|
|
||
| // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $table is safe prefix, $where is prepared above. | ||
| $sql = "SELECT timestamp, level, message FROM {$table} {$where} ORDER BY timestamp DESC"; | ||
| // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- SQL is safely constructed above with $wpdb->prepare(). | ||
| $results = $wpdb->get_results( $sql, ARRAY_A ); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the full implementation of get_db_entries() in Logs.php
rg -A 30 "function get_db_entries" includes/API/Logs.phpRepository: wcpos/woocommerce-pos
Length of output: 1086
🏁 Script executed:
# Search for all calls to get_db_entries() to understand how results are used
rg "get_db_entries" includes/API/Logs.phpRepository: wcpos/woocommerce-pos
Length of output: 211
🏁 Script executed:
# Check for pagination/slicing operations in Logs.php
rg "array_slice|LIMIT|OFFSET|paginate" includes/API/Logs.phpRepository: wcpos/woocommerce-pos
Length of output: 126
🏁 Script executed:
# Get context around the array_slice call to understand the full function/method
rg -B 10 -A 10 "array_slice" includes/API/Logs.phpRepository: wcpos/woocommerce-pos
Length of output: 613
🏁 Script executed:
# Look for unread count related code
rg "unread|count" includes/API/Logs.phpRepository: wcpos/woocommerce-pos
Length of output: 384
🏁 Script executed:
# Get the full Logs.php file to understand the overall structure
wc -l includes/API/Logs.phpRepository: wcpos/woocommerce-pos
Length of output: 90
🏁 Script executed:
# Get the full get_unread_counts function to see if it uses get_db_entries
rg -A 30 "public static function get_unread_counts" includes/API/Logs.phpRepository: wcpos/woocommerce-pos
Length of output: 918
🏁 Script executed:
# Check if get_unread_counts calls get_db_entries
rg -B 5 -A 40 "function get_unread_counts" includes/API/Logs.phpRepository: wcpos/woocommerce-pos
Length of output: 1220
Implement server-side pagination in get_db_entries() to avoid loading entire log table.
get_db_entries() loads all rows without LIMIT/OFFSET, then pagination is applied via PHP array_slice(). This is inefficient for large log tables and impacts both page loads and the get_unread_counts() method, which calls get_db_entries() to count unread entries by iterating through every row in PHP.
Pass $limit and $offset parameters to get_db_entries() and apply them in the SQL query. Similarly, optimize get_unread_counts() to use a COUNT query with appropriate WHERE conditions instead of loading all rows into memory.
🤖 Prompt for AI Agents
In `@includes/API/Logs.php` around lines 197 - 218, get_db_entries currently loads
the entire log table into PHP; add optional $limit and $offset parameters to
get_db_entries($level = null, ?int $limit = null, ?int $offset = null) and
incorporate them into the SQL as prepared LIMIT/OFFSET clauses (use
$wpdb->prepare with %d placeholders) so the query returns only the requested
page; then change get_unread_counts to avoid calling get_db_entries for counting
and instead run a single prepared COUNT(*) query on the same $wpdb->prefix .
'woocommerce_log' table with the same source = 'woocommerce-pos' and level
severity condition to compute unread counts directly in SQL.
E2E UI Test Results18 tests 10 ✅ 5m 24s ⏱️ For more details on these failures, see this check. Results for commit 9b8094e. ♻️ This comment has been updated with latest results. |
PHPUnit Test Results720 tests 712 ✅ 54s ⏱️ Results for commit 9b8094e. ♻️ This comment has been updated with latest results. |
- Clamp per_page/page params to positive integers - Normalize timestamps with strtotime() in get_unread_counts() to handle ISO 8601 vs MySQL datetime format mismatch - Add optimistic rollback on markLogsRead failure - Remove machine-specific path from plan doc
f3193c4 to
9b8094e
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@packages/settings/src/screens/logs/index.tsx`:
- Around line 150-169: The expanded row index (expandedIndex) can persist across
pages and expand the wrong entry when the page changes; update the pagination
handlers or lifecycle to clear it whenever page changes—e.g., when calling
setPage (used in the Previous and Next button onClick handlers) also call the
setter for expandedIndex (setExpandedIndex) to reset to null/undefined/-1, or
add a useEffect that watches page and resets expandedIndex; ensure you reference
and update expandedIndex and setExpandedIndex in the component that renders the
pagination.
- Around line 121-138: The log entry container currently uses a non-focusable
div with an onClick to toggle expansion; replace it with a semantic interactive
element (preferably a <button>) so keyboard users can focus and activate it,
move the onClick handler (onClick={() => isLong && setExpandedIndex(isExpanded ?
null : index)}) to that button, add aria-expanded={isExpanded} and ensure the
disabled state or pointer-events are applied when isLong is false (so
non-expandable entries aren’t interactive), and preserve existing classes
(including LEVEL_STYLES, entry.timestamp, displayMessage) so visual styling
remains unchanged.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@packages/settings/src/screens/logs/index.tsx`:
- Around line 109-149: The expandedIndex state can point to the wrong row after
entries are refreshed and reordered; add logic to reset expandedIndex when the
entries array changes by using an effect that watches entries and calls
setExpandedIndex(null) (or appropriate default) to clear expansion; locate the
state and setter for expandedIndex and add a useEffect referencing entries to
clear it whenever entries updates to avoid stale expanded rows in the list
rendering code that maps entries.
Summary
Closes #504
GET /wcpos/v1/logs,POST /wcpos/v1/logs/mark-read) that reads log entries from both file-based and database WooCommerce log handlersTest plan
vendor/bin/phpunit --filter Test_Logs_Controllerpasses (14 tests)Summary by CodeRabbit
New Features
Translations
Tests