Skip to content

feat: Add log pagination#3261

Open
dblythy wants to merge 6 commits intoparse-community:alphafrom
dblythy:feat/log-pagination
Open

feat: Add log pagination#3261
dblythy wants to merge 6 commits intoparse-community:alphafrom
dblythy:feat/log-pagination

Conversation

@dblythy
Copy link
Member

@dblythy dblythy commented Mar 13, 2026

Summary

  • Add "Load more logs" button to the Logs page for traversing older log entries
  • Update getLogs API method to support Parse Server's from, until, size, and order parameters
  • Logs are appended incrementally using timestamp-based cursor pagination

Closes #376

Summary by CodeRabbit

  • New Features
    • Added pagination to the Logs view with a "Load more logs" button and loading state; includes new styling for the control.
  • Enhancements
    • Log fetching now supports ranged queries, cursor-based paging, ordering, and adjustable page sizes for more efficient retrieval.
  • Chores
    • Updated ignore rules to include .worktrees.

@parse-github-assistant
Copy link

🚀 Thanks for opening this pull request! We appreciate your effort in improving the project. Please let us know once your pull request is ready for review.

Note

Please respond to review comments from AI agents just like you would to comments from a human reviewer. Let the reviewer resolve their own comments, unless they have reviewed and accepted your commit, or agreed with your explanation for why the feedback was incorrect.

Caution

Pull requests must be written using an AI agent with human supervision. Pull requests written entirely by a human will likely be rejected, because of lower code quality, higher review effort and the higher risk of introducing bugs. Please note that AI review comments on this pull request alone do not satisfy this requirement.

@parseplatformorg
Copy link
Contributor

parseplatformorg commented Mar 13, 2026

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@coderabbitai
Copy link

coderabbitai bot commented Mar 13, 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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a368819f-83e8-43cd-828c-63844d353c20

📥 Commits

Reviewing files that changed from the base of the PR and between 7dae480 and 8cb0618.

📒 Files selected for processing (1)
  • src/dashboard/Data/Logs/Logs.react.js

📝 Walkthrough

Walkthrough

Adds cursor-based pagination to dashboard logs: Logs component gains loading, hasMore, latestLogsRequestId, handleLoadMore() and fetchLogs(app,type,until) to append older entries; ParseApp.getLogs now accepts { from, until, size, order }; .worktrees was added to .gitignore; new .showMore CSS class added.

Changes

Cohort / File(s) Summary
Gitignore Configuration
\.gitignore
Added .worktrees to AI tools ignore rules.
Log Pagination UI & Styles
src/dashboard/Data/Logs/Logs.react.js, src/dashboard/Data/Logs/Logs.scss
Added loading, hasMore, latestLogsRequestId, handleLoadMore(); updated fetchLogs(app, type, until) to request pages, deduplicate/append older logs, and render a conditional "Load more logs" Button. Added .showMore CSS class.
Log API Wrapper
src/lib/ParseApp.js
Changed getLogs(level, since)getLogs(level, { from, until, size = 100, order } = {}); updated query parameter construction to use from, until, size, and order for paginated requests.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as Logs.react.js
    participant API as ParseApp.getLogs()
    participant Server as Backend API

    User->>UI: Click "Load more logs"
    UI->>UI: handleLoadMore() — compute until cursor, guard loading
    UI->>UI: setState(loading = true)
    UI->>API: getLogs(level, { until: cursor, size: PAGE_SIZE, order })
    API->>Server: GET /logs?level=...&until=...&size=...&order=...
    Server-->>API: return logs[]
    API-->>UI: resolve logs[]
    UI->>UI: setState(loading = false, append/deduplicate logs, update hasMore)
    UI-->>User: render additional logs (toggle Load more)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: Add log pagination' accurately describes the main feature addition of implementing pagination functionality for logs in the dashboard.
Description check ✅ Passed The PR description covers the summary and approach, and identifies a linked issue. However, the 'Tasks' section with test and documentation checkboxes is omitted from the provided description.
Linked Issues check ✅ Passed The PR successfully implements log pagination via a 'Load more logs' button and cursor-based pagination [#376], updated getLogs API to support from/until/size/order parameters [#376], and addresses the core requirement of enabling log traversal without verbose logging [#376].
Out of Scope Changes check ✅ Passed All changes are directly aligned with log pagination requirements: fetchLogs enhancements, pagination UI, CSS styling for the button, and API method updates. The .gitignore modification for .worktrees is a minor configuration update that does not conflict with the pagination scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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
📝 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.

Copy link

@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

Caution

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

⚠️ Outside diff range comments (1)
src/dashboard/Data/Logs/Logs.react.js (1)

141-143: ⚠️ Potential issue | 🟡 Minor

Potential React key collision with object timestamps.

The timestamp used as key may be an object (with an .iso property), which would stringify to "[object Object]" causing key collisions. This is consistent with the handling in handleLoadMore at line 81.

🔧 Suggested fix for consistent key extraction
-            {this.state.logs.map(({ message, timestamp }) => (
-              <LogViewEntry key={timestamp} text={message} timestamp={timestamp} />
+            {this.state.logs.map(({ message, timestamp }) => {
+              const timestampKey = timestamp?.iso || timestamp;
+              return (
+                <LogViewEntry key={timestampKey} text={message} timestamp={timestamp} />
+              );
+            }
            ))}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/dashboard/Data/Logs/Logs.react.js` around lines 141 - 143, The map in
Logs.react.js uses timestamp as the React key but timestamps can be objects
(with an .iso field) which stringifies to "[object Object]" and can collide;
update the map in the render (the this.state.logs.map invocation that renders
LogViewEntry) to derive a stable key the same way handleLoadMore does (e.g. use
timestamp.iso when present, otherwise fallback to timestamp) and pass the
original timestamp through to LogViewEntry for display.
🧹 Nitpick comments (1)
src/dashboard/Data/Logs/Logs.react.js (1)

75-83: Consider guarding against duplicate requests.

handleLoadMore can be invoked multiple times while a request is in flight since the button with progress={true} is not disabled. This could result in duplicate log entries being appended.

♻️ Suggested fix to prevent duplicate requests
  handleLoadMore() {
    const logs = this.state.logs;
-   if (!logs || logs.length === 0) {
+   if (!logs || logs.length === 0 || this.state.loading) {
      return;
    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/dashboard/Data/Logs/Logs.react.js` around lines 75 - 83, handleLoadMore
can trigger duplicate fetches because it doesn't check whether a request is
already in flight; add a guard (e.g., this.state.isLoading or this._isFetching)
and return early if set, set the flag immediately before calling
fetchLogs(this.context, this.props.params.type, oldestTimestamp) and ensure
fetchLogs (or its completion handlers) clears the flag on both success and error
so subsequent clicks can proceed; reference handleLoadMore, this.state.logs, and
fetchLogs to locate where to add the guard and where to clear the flag.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/dashboard/Data/Logs/Logs.react.js`:
- Around line 71-72: The pagination error handler currently calls
this.setState({ logs: [], hasMore: false, loading: false }) which wipes
previously loaded entries; change the handler to preserve existing logs by not
resetting the logs array (use this.setState with only hasMore and loading or use
functional setState to keep prevState.logs), and only update hasMore/loading
appropriately (e.g., set loading:false and optionally keep hasMore unchanged or
set it to false) so failures don't discard already fetched data; update the
error path where this.setState is invoked (the pagination/fetch error callback)
to implement this behavior.

---

Outside diff comments:
In `@src/dashboard/Data/Logs/Logs.react.js`:
- Around line 141-143: The map in Logs.react.js uses timestamp as the React key
but timestamps can be objects (with an .iso field) which stringifies to "[object
Object]" and can collide; update the map in the render (the this.state.logs.map
invocation that renders LogViewEntry) to derive a stable key the same way
handleLoadMore does (e.g. use timestamp.iso when present, otherwise fallback to
timestamp) and pass the original timestamp through to LogViewEntry for display.

---

Nitpick comments:
In `@src/dashboard/Data/Logs/Logs.react.js`:
- Around line 75-83: handleLoadMore can trigger duplicate fetches because it
doesn't check whether a request is already in flight; add a guard (e.g.,
this.state.isLoading or this._isFetching) and return early if set, set the flag
immediately before calling fetchLogs(this.context, this.props.params.type,
oldestTimestamp) and ensure fetchLogs (or its completion handlers) clears the
flag on both success and error so subsequent clicks can proceed; reference
handleLoadMore, this.state.logs, and fetchLogs to locate where to add the guard
and where to clear the flag.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 27af2e24-2055-4ba0-8b92-0d61fc1ea729

📥 Commits

Reviewing files that changed from the base of the PR and between 63d9019 and 695e4fb.

📒 Files selected for processing (4)
  • .gitignore
  • src/dashboard/Data/Logs/Logs.react.js
  • src/dashboard/Data/Logs/Logs.scss
  • src/lib/ParseApp.js

The error handler was resetting logs to an empty array, discarding
previously fetched entries. Use functional setState to keep existing
logs on failure.
@dblythy dblythy requested a review from mtrezza March 13, 2026 11:12
Copy link

@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)
src/dashboard/Data/Logs/Logs.react.js (1)

149-158: Consider disabling the button while loading to prevent concurrent requests.

The button shows a loading indicator via progress, but users may still be able to click it repeatedly, triggering multiple concurrent pagination requests. Adding disabled={this.state.loading} would prevent this.

💡 Suggested improvement
              <Button
                progress={this.state.loading}
+               disabled={this.state.loading}
                color="blue"
                value="Load more logs"
                onClick={() => this.handleLoadMore()}
              />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/dashboard/Data/Logs/Logs.react.js` around lines 149 - 158, The Load more
Button currently uses progress={this.state.loading} but can still be clicked
while loading; update the Button in the hasMore block to include
disabled={this.state.loading} so clicks are ignored during an ongoing request
(the element to change is the Button with onClick={() => this.handleLoadMore()}
in the render that checks this.state.hasMore), ensuring handleLoadMore cannot be
invoked concurrently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/dashboard/Data/Logs/Logs.react.js`:
- Around line 79-87: The pagination sends the oldest log's timestamp as the
inclusive `until` to fetchLogs, causing duplicates; in handleLoadMore (and where
fetchLogs is called) convert oldestLog.timestamp to a numeric millisecond value
and pass an `until` that is one millisecond less (e.g., oldestTimestampMs - 1)
so Parse's inclusive <= bound excludes the already-seen log; ensure you handle
ISO string timestamps (parse to Date) and non-numeric timestamp forms before
subtracting, or alternatively implement client-side deduplication in the code
that merges fetched logs by unique id (e.g., log.id) if you prefer that
approach.

---

Nitpick comments:
In `@src/dashboard/Data/Logs/Logs.react.js`:
- Around line 149-158: The Load more Button currently uses
progress={this.state.loading} but can still be clicked while loading; update the
Button in the hasMore block to include disabled={this.state.loading} so clicks
are ignored during an ongoing request (the element to change is the Button with
onClick={() => this.handleLoadMore()} in the render that checks
this.state.hasMore), ensuring handleLoadMore cannot be invoked concurrently.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2f75a819-59f4-45e6-a6bc-c9c9f475a4e4

📥 Commits

Reviewing files that changed from the base of the PR and between 695e4fb and 71a965b.

📒 Files selected for processing (1)
  • src/dashboard/Data/Logs/Logs.react.js

Copy link

@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

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

Inline comments:
In `@src/dashboard/Data/Logs/Logs.react.js`:
- Around line 79-88: handleLoadMore can trigger concurrent identical fetches
causing duplicate concatenation; add a concurrency guard (e.g.,
this.state.isLoadingMore or this._isLoadingMore flag) and check it at the top of
handleLoadMore to return early if a load is in progress, set the flag
immediately before calling fetchLogs(this.context, this.props.params.type,
exclusiveUntil) and clear it when that fetch completes (either in the promise
resolution/rejection path inside fetchLogs or in the callback that merges logs)
so duplicate responses won't be concatenated; update any UI "Load more" control
to read the same flag to disable during in-flight requests.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 16292604-ae0b-41ef-8ad0-f9ef1c55ecee

📥 Commits

Reviewing files that changed from the base of the PR and between 71a965b and af59ac5.

📒 Files selected for processing (1)
  • src/dashboard/Data/Logs/Logs.react.js

coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 13, 2026
Copy link

@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 `@src/dashboard/Data/Logs/Logs.react.js`:
- Line 67: The current hasMore logic uses newLogs.length >= PAGE_SIZE which
relies on a server page-size contract; instead, in the fetch/pagination logic
(the function that produces newLogs — e.g., loadMoreLogs/fetchLogs in
Logs.react.js), set hasMore based on whether the request returned any rows:
hasMore should be true when newLogs.length > 0 and become false only when a
request returns zero rows. Update the assignment that sets hasMore (referencing
newLogs and PAGE_SIZE) to this safer fallback so we stop only when an empty
result is observed.
- Around line 61-75: The callbacks from app.getLogs need to ignore stale
responses: create a per-request token (e.g., this.latestLogsRequestId) and
increment it before calling app.getLogs; capture the token in the promise
handlers for the call that fetches logs and, in both the success handler (where
you use PAGE_SIZE and update logs/hasMore/loading) and the error handler (which
currently sets logs/hasMore/loading), bail early if the captured token !==
this.latestLogsRequestId so stale info/flags aren’t applied to state; keep
existing logic for the until branch and for populating logs if the token
matches.
- Around line 84-87: The current pagination subtracts 1ms from the oldest
timestamp (exclusiveUntil) which can permanently drop rows that share the same
timestamp; instead avoid shifting the time cursor and deduplicate incoming logs
on the client. Update the logic around oldestLog/oldestTimestamp/exclusiveUntil
and fetchLogs so you pass the raw oldestTimestamp (no -1ms) or keep it as-is,
but implement deduplication when appending results from fetchLogs: compare each
incoming log's unique identifier (e.g., log.id or a stable composite key of
timestamp+message/source) against the existing logs array and filter out
duplicates before merging; put this dedupe step in the method that processes
fetchLogs responses in Logs.react.js so repeated timestamps no longer lose rows.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e9bdddbc-c357-475d-a4ca-4440bddaa7c5

📥 Commits

Reviewing files that changed from the base of the PR and between af59ac5 and 7dae480.

📒 Files selected for processing (1)
  • src/dashboard/Data/Logs/Logs.react.js

…logic

- Add request token to ignore stale fetch responses when user navigates
- Deduplicate incoming logs by timestamp+message composite key instead
  of subtracting 1ms from the pagination cursor (which dropped rows)
- Base hasMore on whether any rows were returned rather than matching
  PAGE_SIZE exactly
@mtrezza
Copy link
Member

mtrezza commented Mar 13, 2026

Please include a screenshot or video of the implemented UI change.

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 Logs without Verbose

3 participants