Skip to content

fix: hide view, download and filter buttons when there are no records - #108

Open
maikschneider wants to merge 1 commit into
masterfrom
fix/hide-controls-empty-state
Open

fix: hide view, download and filter buttons when there are no records#108
maikschneider wants to merge 1 commit into
masterfrom
fix/hide-controls-empty-state

Conversation

@maikschneider

@maikschneider maikschneider commented Jul 21, 2026

Copy link
Copy Markdown
Member

Resolves #107.

Problem

When a storage contains no records at all, the list module shows only the empty-state infobox — but the doc-header still rendered the view, download and filter buttons. With zero records these are dead controls (nothing to filter, no columns to configure, an empty export).

Change

In AbstractBackendController::configureModuleTemplateDocHeader(), the view dropdown, download and filter-toggle buttons are now only registered when getFullRecordCount() > 0.

  • The New button and the language/page/table selectors stay — they remain useful with an empty list.
  • getFullRecordCount() is memoized (?int $fullRecordCount) so the guard reuses the count already fetched in assignViewVariables() instead of firing a second query.

Why fullRecordCount, not recordCount

The guard checks the total record count, not the filtered result count. When records exist but a search/filter returns zero matches, the buttons still show so the user can reset the filter — that path is unchanged.

Verification

  • ddev php -l passes.
  • No concrete controller overrides configureModuleTemplateDocHeader() or getFullRecordCount(), so the guard applies to all list modules.

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of empty result sets by caching record counts.
    • Prevented view, download, and filter controls from appearing when no records are available.
    • Reduced unnecessary repeated record-count queries for improved performance.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@maikschneider, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 55 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: de4b46f3-0055-41a5-9608-ea50139104c4

📥 Commits

Reviewing files that changed from the base of the PR and between 70698d1 and a97b352.

📒 Files selected for processing (1)
  • Classes/Controller/AbstractBackendController.php

Walkthrough

AbstractBackendController now memoizes the full record count, including zero when no record PIDs exist or the query returns no count. The document-header configuration checks this count before adding the view dropdown, download button, and filter toggle buttons, so these controls are omitted when the storage contains no records.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarizes the main behavior change in the PR.
Linked Issues check ✅ Passed The change hides view, download, and filter controls only when fullRecordCount is zero, while leaving the New button and selectors available.
Out of Scope Changes check ✅ Passed The added count memoization supports the same control-visibility fix and does not introduce unrelated behavior.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/hide-controls-empty-state

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.

@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.

Caution

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

⚠️ Outside diff range comments (1)
Classes/Controller/AbstractBackendController.php (1)

677-701: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

Prevent unnecessary queries on error paths and improve type safety.

There are two minor improvements to make here:

  1. Unnecessary queries: When the user has no accessible storage (getAccessiblePids() === []) but the request specifies a single scope (e.g., ?scope=single&id=123), getRequestedPids() will return that ID. Because renderNoStorageResponse() eventually triggers this method to populate view variables, it results in an unnecessary (and technically unauthorized) database query. Short-circuiting if getAccessiblePids() is empty prevents this.
  2. Type safety: Depending on the database driver, the count value returned by the query could be a string. Casting the result to (int) guarantees strict type safety when assigning it to the ?int $fullRecordCount property. Additionally, using fetchOne() is cleaner and more idiomatic in Doctrine DBAL for fetching a single scalar value like COUNT(*).
💡 Proposed refactor
     protected function getFullRecordCount(): int
     {
         if ($this->fullRecordCount !== null) {
             return $this->fullRecordCount;
         }
 
-        if ($this->getRequestedPids() === []) {
+        $pids = $this->getRequestedPids();
+        if ($this->getAccessiblePids() === [] || $pids === []) {
             return $this->fullRecordCount = 0;
         }
 
         $tableName = $this->getTableName();
         $qb = $this->connectionPool->getQueryBuilderForTable($tableName);
         $qb->getRestrictions()->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this::WORKSPACE_ID));
         $qb->getRestrictions()->removeByType(HiddenRestriction::class);
 
         $count = $qb->count('*')
             ->from($tableName)
             ->where(
-                $qb->expr()->in('pid', $qb->quoteArrayBasedValueListToIntegerList($this->getRequestedPids()))
+                $qb->expr()->in('pid', $qb->quoteArrayBasedValueListToIntegerList($pids))
             )
             ->executeQuery()
-            ->fetchNumeric();
+            ->fetchOne();
 
-        return $this->fullRecordCount = ($count ? $count[0] : 0);
+        return $this->fullRecordCount = (int)$count;
     }
🤖 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 `@Classes/Controller/AbstractBackendController.php` around lines 677 - 701,
Update getFullRecordCount() to return and cache 0 before querying when
getAccessiblePids() is empty, including the existing empty-requested-PIDs case.
Fetch the COUNT result with fetchOne() and cast it to int before assigning it to
the fullRecordCount property.
🤖 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.

Outside diff comments:
In `@Classes/Controller/AbstractBackendController.php`:
- Around line 677-701: Update getFullRecordCount() to return and cache 0 before
querying when getAccessiblePids() is empty, including the existing
empty-requested-PIDs case. Fetch the COUNT result with fetchOne() and cast it to
int before assigning it to the fullRecordCount property.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: da313ade-b613-4125-ac43-67ddedbeca87

📥 Commits

Reviewing files that changed from the base of the PR and between f674878 and 70698d1.

📒 Files selected for processing (1)
  • Classes/Controller/AbstractBackendController.php

When a storage contains no records at all, only the empty-state infobox
is shown. The view, download and filter doc-header buttons had nothing to
act on and were dead controls, so they are now skipped when
fullRecordCount is 0. The count is memoized to avoid a second query.

The guard uses fullRecordCount, not recordCount, so the buttons still
show when records exist but a search returns no matches.

Resolves #107
@maikschneider
maikschneider force-pushed the fix/hide-controls-empty-state branch from 70698d1 to a97b352 Compare July 21, 2026 11:12
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.

Hide view, download and filter buttons when there are no records

1 participant