Skip to content

moved case studies filtering to frontend#272

Merged
vasilyyaremchuk merged 2 commits into
mainfrom
case-studies-index-fix
Jul 6, 2026
Merged

moved case studies filtering to frontend#272
vasilyyaremchuk merged 2 commits into
mainfrom
case-studies-index-fix

Conversation

@sf-alexo

@sf-alexo sf-alexo commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Moved case study filtering from server-side URL navigation to a client-side experience. The listing page now preloads case-study, tag, and partner data, while the frontend search handler manages live keyword and tag filtering in memory, updates counts and empty states, and supports add/remove/clear actions without page redirects or infinite-scroll markup.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

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

Next review available in: 49 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: ASSERTIVE

Plan: Pro

Run ID: 4634f7eb-e9a9-4a0b-99c8-eea04afa51ee

📥 Commits

Reviewing files that changed from the base of the PR and between bf5f185 and fd8ae95.

📒 Files selected for processing (1)
  • website/modules/case-studies-page/index.js

Walkthrough

Changes

This PR removes the server-side search/tag-count indexing pipeline from the case-studies-page module (SearchService, TagCountService, and related query-building/relationship-resolution logic), simplifying beforeIndex to load pieces, tags, and partners directly and beforeShow to a single call. The index template is refactored so tag/filter add/remove links and the "clear all" control use javascript:void(0) with data attributes instead of server-built URLs, and the results grid/empty-state are always rendered with visibility toggled via a new .is-hidden CSS class. Infinite-scroll markup and script are removed. search-handler.js is rewritten to perform fully client-side, in-memory tag and keyword filtering, replacing prior URL-navigation-based search behavior.

Sequence Diagram(s)

Included above in the hidden review stack artifact.

Possibly related PRs

🚥 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 accurately summarizes the main change and is concise.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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
Contributor

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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@website/modules/case-studies-page/index.js`:
- Around line 103-112: The case studies index is loading the entire pieces
collection on every render, which can grow unbounded. Update the logic around
self.pieces.find(req).toArray() in the case-studies page handler to fetch only
the fields needed by the cards and data-search, and avoid projecting unused
data. Keep the Promise.all flow for casesTags and businessPartners, but cap the
piece payload so req.data.pieces, totalPieces, and totalPages are based on a
minimal result set. If full corpus loading is still intended, make that
expectation explicit and ensure the dataset is guaranteed to remain small.

In `@website/public/js/modules/case-studies-page/search-handler.js`:
- Around line 192-199: The selected-tag markup in search-handler.js is being
built with innerHTML, which leaves the label and data attributes as an injection
sink. Update the code in the selected-tag creation path to use DOM APIs instead
of a template string: create the li, text node, anchor, and image elements
explicitly, set their textContent and attribute values via setAttribute, and
append them together. Use the existing selected-tag/remove-tag logic and the
label/filterType/value variables as the identifiers to locate and refactor this
block.
- Around line 206-213: The visibility update in the search state handling only
toggles is-hidden, so .items-count__mobile never gets the class it needs to
become visible. Update the logic in the search-handler flow where
selectedTagsContainer, itemsCount, itemsCountMobile, and clearAll are toggled so
the mobile count also receives the matching is-visible state (or adjust the
related SCSS contract consistently). Use the existing itemsCountMobile symbol in
that block to keep the desktop and mobile visibility behavior aligned.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: dea37748-4992-492b-883e-51fdf73fb2fd

📥 Commits

Reviewing files that changed from the base of the PR and between 20d49c3 and bf5f185.

📒 Files selected for processing (4)
  • website/modules/asset/ui/src/scss/_cases.scss
  • website/modules/case-studies-page/index.js
  • website/modules/case-studies-page/views/index.html
  • website/public/js/modules/case-studies-page/search-handler.js

Comment on lines +103 to +112
const [pieces, casesTags, businessPartners] = await Promise.all([
self.pieces.find(req).toArray(),
self.apos.modules['cases-tags'].find(req).toArray(),
self.apos.modules['business-partner'].find(req).toArray(),
]);
req.data.pieces = pieces;
req.data.totalPieces = pieces.length;
req.data.totalPages = 1;
req.data.casesTags = casesTags;
req.data.businessPartners = businessPartners;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🔵 Trivial

Unbounded load of every case study on each index render.

self.pieces.find(req).toArray() pulls the full corpus (with all projected fields) on every request to hydrate client-side filtering. As the number of case studies grows this increases DB load, memory, and rendered page size linearly with no ceiling. Consider capping the projection to only the fields the cards/data-search actually need, and confirm the total volume is expected to stay small.

🤖 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 `@website/modules/case-studies-page/index.js` around lines 103 - 112, The case
studies index is loading the entire pieces collection on every render, which can
grow unbounded. Update the logic around self.pieces.find(req).toArray() in the
case-studies page handler to fetch only the fields needed by the cards and
data-search, and avoid projecting unused data. Keep the Promise.all flow for
casesTags and businessPartners, but cap the piece payload so req.data.pieces,
totalPieces, and totalPages are based on a minimal result set. If full corpus
loading is still intended, make that expectation explicit and ensure the dataset
is guaranteed to remain small.

Comment on lines +192 to +199
const li = document.createElement('li');
li.className = 'selected-tag';
li.innerHTML = `
${label}
<a class="remove-tag" href="javascript:void(0)" data-filter-type="${filterType}" data-tag-value="${value}" aria-label="Remove ${filterType} tag ${label}">
<img src="/images/close.svg" alt="Close Icon">
</a>
`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Building selected-tag markup via innerHTML is an injection sink.

label (from data-tag-label, i.e. the tag title) and the interpolated attribute values are written into innerHTML unescaped. Any markup or quote in a tag title/value breaks out of the template and can inject script (stored XSS via editorial content). Build the node with createElement/textContent instead of a template string.

🔒️ Proposed fix using DOM APIs
-          const li = document.createElement('li');
-          li.className = 'selected-tag';
-          li.innerHTML = `
-            ${label}
-            <a class="remove-tag" href="javascript:void(0)" data-filter-type="${filterType}" data-tag-value="${value}" aria-label="Remove ${filterType} tag ${label}">
-              <img src="/images/close.svg" alt="Close Icon">
-            </a>
-          `;
-          selectedTagsList.appendChild(li);
+          const li = document.createElement('li');
+          li.className = 'selected-tag';
+          li.appendChild(document.createTextNode(`${label} `));
+          const removeLink = document.createElement('a');
+          removeLink.className = 'remove-tag';
+          removeLink.href = 'javascript:void(0)';
+          removeLink.dataset.filterType = filterType;
+          removeLink.dataset.tagValue = value;
+          removeLink.setAttribute('aria-label', `Remove ${filterType} tag ${label}`);
+          const img = document.createElement('img');
+          img.src = '/images/close.svg';
+          img.alt = 'Close Icon';
+          removeLink.appendChild(img);
+          li.appendChild(removeLink);
+          selectedTagsList.appendChild(li);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const li = document.createElement('li');
li.className = 'selected-tag';
li.innerHTML = `
${label}
<a class="remove-tag" href="javascript:void(0)" data-filter-type="${filterType}" data-tag-value="${value}" aria-label="Remove ${filterType} tag ${label}">
<img src="/images/close.svg" alt="Close Icon">
</a>
`;
const li = document.createElement('li');
li.className = 'selected-tag';
li.appendChild(document.createTextNode(`${label} `));
const removeLink = document.createElement('a');
removeLink.className = 'remove-tag';
removeLink.href = 'javascript:void(0)';
removeLink.dataset.filterType = filterType;
removeLink.dataset.tagValue = value;
removeLink.setAttribute('aria-label', `Remove ${filterType} tag ${label}`);
const img = document.createElement('img');
img.src = '/images/close.svg';
img.alt = 'Close Icon';
removeLink.appendChild(img);
li.appendChild(removeLink);
selectedTagsList.appendChild(li);
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 193-198: Avoid assigning untrusted data to innerHTML/outerHTML or document.write
Context: li.innerHTML = ${label} <a class="remove-tag" href="javascript:void(0)" data-filter-type="${filterType}" data-tag-value="${value}" aria-label="Remove ${filterType} tag ${label}"> <img src="/images/close.svg" alt="Close Icon"> </a>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').

(inner-outer-html)

🪛 OpenGrep (1.23.0)

[WARNING] 194-199: Setting innerHTML with dynamic content can lead to XSS. Use textContent or createElement with proper escaping instead.

(coderabbit.xss.innerhtml-assignment)

🤖 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 `@website/public/js/modules/case-studies-page/search-handler.js` around lines
192 - 199, The selected-tag markup in search-handler.js is being built with
innerHTML, which leaves the label and data attributes as an injection sink.
Update the code in the selected-tag creation path to use DOM APIs instead of a
template string: create the li, text node, anchor, and image elements
explicitly, set their textContent and attribute values via setAttribute, and
append them together. Use the existing selected-tag/remove-tag logic and the
label/filterType/value variables as the identifiers to locate and refactor this
block.

Source: Linters/SAST tools

Comment on lines +206 to +213
const itemsCount = document.querySelector('.items-count');
const itemsCountMobile = document.querySelector('.items-count__mobile');
const clearAll = document.querySelector('.clear-all');

[selectedTagsContainer, itemsCount, itemsCountMobile, clearAll].forEach((el) => {
if (!el) return;
el.classList.toggle('is-hidden', !hasActiveFilters);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm nothing else toggles is-visible on items-count__mobile
rg -nP "items-count__mobile|is-visible" website/public/js website/modules -g '!**/node_modules/**'

Repository: speedandfunction/website

Length of output: 1165


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== search-handler around the reported lines ==\n'
sed -n '180,225p' website/public/js/modules/case-studies-page/search-handler.js

printf '\n== template snippet for items-count__mobile ==\n'
sed -n '70,95p' website/modules/case-studies-page/views/index.html

printf '\n== SCSS snippet for items-count__mobile ==\n'
sed -n '580,605p' website/modules/asset/ui/src/scss/_cases.scss

printf '\n== filterModal usage of is-visible ==\n'
sed -n '60,125p' website/modules/asset/ui/src/filterModal.js

Repository: speedandfunction/website

Length of output: 5706


Toggle the mobile visibility class here too. website/public/js/modules/case-studies-page/search-handler.js:206-213 only flips is-hidden, but .items-count__mobile stays display: none unless is-visible is added. Add the matching class toggle here or change the SCSS contract.

🤖 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 `@website/public/js/modules/case-studies-page/search-handler.js` around lines
206 - 213, The visibility update in the search state handling only toggles
is-hidden, so .items-count__mobile never gets the class it needs to become
visible. Update the logic in the search-handler flow where
selectedTagsContainer, itemsCount, itemsCountMobile, and clearAll are toggled so
the mobile count also receives the matching is-visible state (or adjust the
related SCSS contract consistently). Use the existing itemsCountMobile symbol in
that block to keep the desktop and mobile visibility behavior aligned.

@vasilyyaremchuk vasilyyaremchuk merged commit cb77d36 into main Jul 6, 2026
10 of 11 checks passed
@vasilyyaremchuk vasilyyaremchuk deleted the case-studies-index-fix branch July 6, 2026 13:58
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.

3 participants