Skip to content

[595] implement category display in sidebar for case studies#106

Merged
IhorMasechko merged 10 commits intomainfrom
595-display-created-categories-in-the-sidebar
May 23, 2025
Merged

[595] implement category display in sidebar for case studies#106
IhorMasechko merged 10 commits intomainfrom
595-display-created-categories-in-the-sidebar

Conversation

@IhorMasechko
Copy link
Copy Markdown
Contributor

This PR implements a new categorization system for case study tags, improving the organization and user experience of the case studies page.

Changes:

  • Added category grouping for case study tags in the sidebar
  • Implemented a new helper function groupTagsByCategory to organize tags by their categories
  • Updated the case studies page template to display tags grouped by category
  • Added category relationship to case study tags
  • Updated tag filtering to maintain category context

Technical Details:

  • Added _category relationship to case study tags
  • Created a new helper function in the case studies module to group tags by category
  • Modified the sidebar template to display tags under their respective category headers
  • Updated the tag filtering system to work with the new categorized structure

Testing:

  • Verify that tags are properly grouped by category in the sidebar
  • Ensure tag filtering still works correctly with the new categorization
  • Check that uncategorized tags appear under "Uncategorized" section
  • Verify that the UI remains responsive and user-friendly

@IhorMasechko IhorMasechko requested a review from killev as a code owner May 21, 2025 07:10
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented May 21, 2025

📝 Walkthrough

Walkthrough

The changes refactor the case studies filtering and display system to use three distinct filter categories—industry, stack (technology), and caseStudyType—replacing a previous single tag filter. Data structures, templates, and admin UI are updated to support multi-valued fields and categorized tag relationships, with new helper methods for tag counting and display.

Changes

File(s) Change Summary
website/modules/case-studies-page/views/index.html, website/modules/case-studies-page/views/show.html Refactored templates to support multi-category filters (industry, stack, caseStudyType) and display multiple values per category by iterating over arrays. Updated filter UI to group tags by category and provide category-specific removal and display logic.
website/modules/case-studies/index.js Renamed fields (stack, caseStudyType, industry) to _stack, _caseStudyType, _industry and converted them to relationship fields referencing cases-tags. Added builder projections, browser filters, and a helper to group tags by category. Updated columns, groups, and filters to use new fields. Added alias caseStudy. Assigned AposCellTags component to _stack column. Removed _tags field entirely.
website/modules/cases-tags/index.js Changed keyboard shortcut, added _category relationship to builder and columns, and introduced a filter for _category to allow tag filtering by category in the admin UI.
website/modules/case-studies-page/index.js Modularized tag counting logic with a helper object, added async tag count calculation, and refactored filter options to support three categories. Added a beforeIndex hook to compute tag counts before rendering and attached them to request data. Added new options for pieces and filters URL.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CaseStudiesPage
    participant CaseStudiesModule
    participant CasesTagsModule
    participant Database

    User->>CaseStudiesPage: Load page with filter(s)
    CaseStudiesPage->>CaseStudiesModule: Fetch case studies with filters (industry, stack, caseStudyType)
    CaseStudiesPage->>CasesTagsModule: Fetch tag metadata
    CaseStudiesPage->>CaseStudiesModule: Calculate tag counts (beforeIndex hook)
    CaseStudiesModule->>Database: Query case studies and tags
    Database-->>CaseStudiesModule: Return data
    CaseStudiesModule-->>CaseStudiesPage: Return case studies and tag counts
    CaseStudiesPage-->>User: Render page with filtered studies, tag counts, and filter UI
Loading

Possibly related PRs

  • speedandfunction/website#48: Introduced the initial case studies page with a single tag filter and basic template; this PR builds upon and refactors those components for multi-category filtering.
  • speedandfunction/website#85: Added initial string fields for caseStudyType and industry to the case studies schema and updated templates; this PR extends those schema and filtering concepts to relationship fields with categorized tags.

Suggested reviewers

  • VitalyyP
  • Anton-88
  • yuramax

Note

⚡️ AI Code Reviews for VS Code, Cursor, Windsurf

CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
Learn more here.


Note

⚡️ Faster reviews with caching

CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 30th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.
Enjoy the performance boost—your workflow just got faster.

✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@IhorMasechko IhorMasechko self-assigned this May 21, 2025
Copy link
Copy Markdown
Contributor

@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: 0

🧹 Nitpick comments (2)
website/modules/case-studies/index.js (1)

179-198: Optimize the category accessor with optional chaining

The helper function correctly groups tags by category, but the nested property check could be simplified using optional chaining.

-        const category =
-          (tag._category && tag._category[0] && tag._category[0].title) ||
-          'Uncategorized';
+        const category = tag._category?.[0]?.title || 'Uncategorized';

Additionally, consider adding a check for the existence of the label property before pushing it to the array to prevent potential undefined values:

-        grouped[category].push(tag.label);
+        if (tag.label) {
+          grouped[category].push(tag.label);
+        }
🧰 Tools
🪛 Biome (1.9.4)

[error] 188-188: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

website/modules/case-studies-page/views/index.html (1)

74-77: Consider optimizing tag matching logic

The current implementation requires additional iteration to find the full tag object that matches each label. This works but could be more efficient.

Consider modifying the groupTagsByCategory helper to retain the full tag objects instead of just the labels. This would eliminate the need for the nested loop to find matching tags:

// In case-studies/index.js:
groupTagsByCategory(tags) {
  const grouped = {};
  if (!Array.isArray(tags)) {
    return grouped;
  }
  tags.forEach((tag) => {
    const category = tag._category?.[0]?.title || 'Uncategorized';
    if (!grouped[category]) {
      grouped[category] = [];
    }
    grouped[category].push(tag); // Store the full tag object
  });
  return grouped;
}

Then in the template:

-{% for label in tagLabels %}
-  {# Find the tag object that matches this label #}
-  {% set matchingTag = null %}
-  {% for tag in tags %}
-    {% if tag.label == label %}
-      {% set matchingTag = tag %}
-    {% endif %}
-  {% endfor %}
-  {% if matchingTag %}
+{% for tag in tagObjects %}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between deadf6b and 0a0dc8d.

📒 Files selected for processing (3)
  • website/modules/case-studies-page/views/index.html (1 hunks)
  • website/modules/case-studies/index.js (3 hunks)
  • website/modules/cases-tags/index.js (1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
website/modules/case-studies/index.js

[error] 188-188: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: e2e-tests
  • GitHub Check: lint
  • GitHub Check: security-scan
  • GitHub Check: unit-tests
🔇 Additional comments (5)
website/modules/case-studies/index.js (2)

12-12: Good addition of the alias!

Adding the alias: 'caseStudy' creates a convenient way to access this piece type from templates, which I can see is used in the case studies page template.


85-85: Properly extending tag relationship to include categories

The inclusion of _category in both the projection and withRelationships ensures that category data is available when retrieving tags, which is essential for the new grouping functionality.

Also applies to: 88-88

website/modules/cases-tags/index.js (1)

7-7:

Details

✅ Verification successful

Keyboard shortcut updated

The keyboard shortcut has been changed from "G,M" to "G,H". Ensure this doesn't conflict with other shortcuts in the system.


🏁 Script executed:

#!/bin/bash
# Check for any other components using the G,H shortcut
rg "shortcut:\s*['\"](G,H|H,G)['\"]" --type js

Length of output: 109


Shortcut “G,H” Conflict Check Passed

A search across JS files found no other occurrences of the shortcut: 'G,H' binding—only in website/modules/cases-tags/index.js. No conflicts detected. Proceed as is.

website/modules/case-studies-page/views/index.html (2)

70-72: Good implementation of categorized tag display

Using the new helper function to group tags by category creates a more organized UI structure. The template now displays category headers, which improves navigation for users.


78-96: Tag filtering logic preserved correctly

The logic for active state identification and URL building for tag filtering has been maintained while incorporating the new category structure. This ensures users can still filter tags as before, now with the added benefit of categorical organization.

coderabbitai[bot]
coderabbitai bot previously approved these changes May 21, 2025
@github-actions
Copy link
Copy Markdown

github-actions bot commented May 21, 2025

🔍 Vulnerabilities of apostrophe-cms:test

📦 Image Reference apostrophe-cms:test
digestsha256:53e9cbebd7fcca2c6951e1c39342040d0c8cf52641408ff5e39f1df4ffabddae
vulnerabilitiescritical: 0 high: 3 medium: 0 low: 0
platformlinux/amd64
size284 MB
packages935
📦 Base Image node:23-alpine
also known as
  • 23-alpine3.21
  • 23.11-alpine
  • 23.11-alpine3.21
  • 23.11.1-alpine
  • 23.11.1-alpine3.21
digestsha256:169ee1b69bd3f5c7c8508a9919e8b367bba9dc7fa9a03097ec9f85d2e8179631
vulnerabilitiescritical: 0 high: 0 medium: 0 low: 0
critical: 0 high: 1 medium: 0 low: 0 connect-multiparty 2.2.0 (npm)

pkg:npm/connect-multiparty@2.2.0

high 7.8: CVE--2022--29623 Unrestricted Upload of File with Dangerous Type

Affected range<=2.2.0
Fixed versionNot Fixed
CVSS Score7.8
CVSS VectorCVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
EPSS Score0.100%
EPSS Percentile29th percentile
Description

An arbitrary file upload vulnerability in the file upload module of Express Connect-Multiparty 2.2.0 allows attackers to execute arbitrary code via a crafted PDF file. NOTE: the Supplier has not verified this vulnerability report.

critical: 0 high: 1 medium: 0 low: 0 async 0.9.2 (npm)

pkg:npm/async@0.9.2

high 7.8: CVE--2021--43138 OWASP Top Ten 2017 Category A9 - Using Components with Known Vulnerabilities

Affected range<2.6.4
Fixed version2.6.4, 3.2.2
CVSS Score7.8
CVSS VectorCVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
EPSS Score1.061%
EPSS Percentile77th percentile
Description

A vulnerability exists in Async through 3.2.1 (fixed in 3.2.2), which could let a malicious user obtain privileges via the mapValues() method.

critical: 0 high: 1 medium: 0 low: 0 async 1.5.2 (npm)

pkg:npm/async@1.5.2

high 7.8: CVE--2021--43138 OWASP Top Ten 2017 Category A9 - Using Components with Known Vulnerabilities

Affected range<2.6.4
Fixed version2.6.4, 3.2.2
CVSS Score7.8
CVSS VectorCVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
EPSS Score1.061%
EPSS Percentile77th percentile
Description

A vulnerability exists in Async through 3.2.1 (fixed in 3.2.2), which could let a malicious user obtain privileges via the mapValues() method.

Copy link
Copy Markdown
Contributor

@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: 0

🧹 Nitpick comments (1)
website/modules/case-studies/index.js (1)

179-199: Well-implemented helper function with room for improvement.

The groupTagsByCategory helper function correctly organizes tags by their category titles, with proper handling of edge cases like non-array inputs and tags without categories. However, the category determination logic could be more concise.

Consider using optional chaining for more concise code:

- const category =
-   (tag._category && tag._category[0] && tag._category[0].title) ||
-   'Uncategorized';
+ const category = tag._category?.[0]?.title || 'Uncategorized';

This makes the code more readable while maintaining the same functionality.

🧰 Tools
🪛 Biome (1.9.4)

[error] 189-189: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0a0dc8d and 1433f82.

📒 Files selected for processing (1)
  • website/modules/case-studies/index.js (3 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
website/modules/case-studies/index.js

[error] 189-189: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (2)
website/modules/case-studies/index.js (2)

12-12: Good addition of the module alias.

Adding the alias caseStudy makes the module methods accessible from templates via apos.caseStudy, which is essential for calling the new helper function from the template as mentioned in the PR summary.


85-85: Properly configured tag-category relationship.

The addition of _category: 1 to the projection and withRelationships: ['_category'] ensures that when tags are fetched, their associated category data is included and populated. This is essential for the grouping functionality to work correctly.

Also applies to: 88-88

coderabbitai[bot]
coderabbitai bot previously approved these changes May 21, 2025
- Refactor case-studies-page/index.js to fix ESLint errors while maintaining functionality
- Split large functions into smaller utility functions
- Fix race condition warnings using defensive copies
- Implement dynamic tag counting with proper modularization
- Update template to use the dynamic tag counts
Copy link
Copy Markdown
Contributor

@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: 5

♻️ Duplicate comments (1)
website/modules/case-studies-page/views/show.html (1)

55-61: Apply the same improvements to _caseStudyType block

The same join optimisation and null-safety comment for _industry applies here. Keeping the two sections consistent reduces maintenance friction.

🧹 Nitpick comments (10)
website/modules/case-studies-page/views/show.html (2)

42-49: Prefer join filter over manual comma logic

Instead of looping and adding a conditional comma, you can lean on Nunjucks’ built-in join filter:

-{% for industry in data.piece._industry %}
-  {{ industry.title }}{% if not loop.last %}, {% endif %}
-{% endfor %}
+{{ data.piece._industry | map(attribute='title') | join(', ') }}

This shortens the template, eliminates the repeated {% if not loop.last %} pattern, and avoids the risk of a trailing comma should the loop logic ever change.


97-104: _stack rendering duplicates earlier patterns

Three nearly identical code blocks (industry, type, stack) repeat the same join/guard logic. Consider extracting a small macro:

{% macro renderList(items) %}
  {{ items | map(attribute='title') | join(', ') }}
{% endmacro %}

Then call {{ _self.renderList(data.piece._stack) }} etc. This keeps the template DRY.

website/modules/case-studies-page/views/index.html (1)

91-153: Repeated filter-type branches violate DRY

Each branch (industry, stack, caseStudyType) duplicates identical HTML except for the query-key. A small macro can collapse ~60 lines:

{% macro tagLink(filter, verb, tag, count) -%}
  <a class="tag-link"
     href="{{ data.url | build({}, { [filter]: { [verb]: tag.value } }) }}">
    {{ tag.label }} [{{ count }}]
  </a>
{%- endmacro %}

Using tagLink(filterType, '$addToSet', tag, count) etc. will simplify maintenance and reduce merge conflicts.

website/app.test.js (1)

9-19: Avoid re-assigning process.env; restore values instead

Reassigning the whole process.env object (process.env = originalEnv) is discouraged in Node.js because other modules may hold references to the original object.
A safer pattern is to mutate keys that were changed during the test and delete the ones you added:

afterEach(() => {
-  process.env = originalEnv;
+  // Restore mutated keys
+  Object.keys(process.env).forEach((k) => {
+    if (!(k in originalEnv)) {
+      delete process.env[k];
+    }
+  });
+  Object.assign(process.env, originalEnv);
});

This keeps the original object instance intact while still rolling back the state.

website/modules/case-studies/index.js (1)

41-67: Minor: browser projection omits slug, which is often useful

If the UI needs the slug for links, consider adding slug: 1 to both builders.project and browser.projection sections of _stack, _industry, and _caseStudyType.
This avoids a follow-up round-trip if slugs become necessary.

website/modules/case-studies-page/index.js (1)

69-108: Performance consideration: cache tag counts

beforeIndex fetches all case studies and tags on every page request, which can be expensive once the collection grows.

Consider caching the computed tagCounts (e.g., in Redis or self.apos.cache) and invalidating on afterInsert, afterUpdate, and afterDelete of case studies or tags.

docs/Infrastructure.md (4)

8-8: Consistent hyphenation for ‘multi-environment’
Consider using a single compound word “multienvironment” or removing the hyphen in line with your style guide to avoid inconsistency.

🧰 Tools
🪛 LanguageTool

[misspelling] ~8-~8: This word is normally spelled as one.
Context: ...tructure**: Modular Terraform setup for multi-environment support * Resource Tags (applied to...

(EN_COMPOUNDS_MULTI_ENVIRONMENT)


69-69: Add hyphen for ‘container-specific’
Update “container specific permissions” to “container-specific permissions” for grammatical correctness.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~69-~69: When ‘container-specific’ is used as a modifier, it is usually spelled with a hyphen.
Context: ...: Grants the application running in the container specific permissions to AWS services * **Resou...

(SPECIFIC_HYPHEN)


85-85: Add missing preposition in ECS Task Execution Role
To improve clarity, consider:

-* Grants permissions to pull container images and send logs
+* Grants permissions to pull container images and to send logs

Or specify the destination: “send logs to CloudWatch.”

🧰 Tools
🪛 LanguageTool

[uncategorized] ~85-~85: Possible missing preposition found.
Context: ...ll container images and send logs * Resource Tags: * `Name: sf-website-ecs-exe...

(AI_HYDRA_LEO_MISSING_TO)


231-231: Remove redundant “only” in TLS line
For brevity, change:

-* Modern TLS protocol versions only
+* Modern TLS protocol versions
🧰 Tools
🪛 LanguageTool

[style] ~231-~231: This adverb was used twice in the sentence. Consider removing one of them or replacing them with a synonym.
Context: ...only * Modern TLS protocol versions only * Security headers added via respon...

(ADVERB_REPETITION_PREMIUM)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1433f82 and a4bdf2b.

📒 Files selected for processing (19)
  • docker-compose.yml (1 hunks)
  • docs/Infrastructure.md (1 hunks)
  • scripts/merged-prs-last-24h.js (1 hunks)
  • website/.eslintrc.js (1 hunks)
  • website/app.js (2 hunks)
  • website/app.test.js (1 hunks)
  • website/e2e/tests/screenshot-test.spec.js (1 hunks)
  • website/modules/@apostrophecms/express/index.js (1 hunks)
  • website/modules/@apostrophecms/form/index.js (2 hunks)
  • website/modules/@apostrophecms/uploadfs/index.js (1 hunks)
  • website/modules/asset/index.js (1 hunks)
  • website/modules/case-studies-page/index.js (2 hunks)
  • website/modules/case-studies-page/views/index.html (4 hunks)
  • website/modules/case-studies-page/views/show.html (2 hunks)
  • website/modules/case-studies/index.js (6 hunks)
  • website/modules/cases-tags/index.js (3 hunks)
  • website/modules/map-widget/views/widget.html (1 hunks)
  • website/utils/env.js (1 hunks)
  • website/utils/env.test.js (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • scripts/merged-prs-last-24h.js
  • website/modules/@apostrophecms/form/index.js
  • website/modules/map-widget/views/widget.html
🚧 Files skipped from review as they are similar to previous changes (1)
  • website/modules/cases-tags/index.js
🧰 Additional context used
🧬 Code Graph Analysis (3)
website/modules/@apostrophecms/express/index.js (3)
website/app.js (1)
  • require (3-3)
website/modules/@apostrophecms/uploadfs/index.js (1)
  • require (1-1)
website/utils/env.js (1)
  • getEnv (2-8)
website/e2e/tests/screenshot-test.spec.js (1)
website/utils/env.js (1)
  • getEnv (2-8)
website/modules/asset/index.js (1)
website/utils/env.js (1)
  • getEnv (2-8)
🪛 Biome (1.9.4)
website/modules/case-studies/index.js

[error] 253-253: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🪛 LanguageTool
docs/Infrastructure.md

[misspelling] ~8-~8: This word is normally spelled as one.
Context: ...tructure**: Modular Terraform setup for multi-environment support * Resource Tags (applied to...

(EN_COMPOUNDS_MULTI_ENVIRONMENT)


[uncategorized] ~69-~69: When ‘container-specific’ is used as a modifier, it is usually spelled with a hyphen.
Context: ...: Grants the application running in the container specific permissions to AWS services * **Resou...

(SPECIFIC_HYPHEN)


[uncategorized] ~85-~85: Possible missing preposition found.
Context: ...ll container images and send logs * Resource Tags: * `Name: sf-website-ecs-exe...

(AI_HYDRA_LEO_MISSING_TO)


[style] ~231-~231: This adverb was used twice in the sentence. Consider removing one of them or replacing them with a synonym.
Context: ...only * Modern TLS protocol versions only * Security headers added via respon...

(ADVERB_REPETITION_PREMIUM)

🔇 Additional comments (30)
website/.eslintrc.js (1)

83-83: LGTM: ESLint rule updated to support loop variables

The ESLint rule for id-length has been modified to allow short variable names (_, i, j) as exceptions. This change aligns with the PR objectives that introduce tag grouping logic, which likely uses these variable names in loops and iterators.

website/utils/env.js (1)

1-12: LGTM: Well-implemented environment variable utility

This new utility properly centralizes environment variable access with clear error handling for undefined variables. The implementation follows best practices by ensuring required environment variables are present and providing descriptive error messages when they're missing.

website/modules/@apostrophecms/express/index.js (2)

1-2: LGTM: Proper import of environment utility

The import statement correctly brings in the getEnv utility with an appropriate relative path.


7-7: LGTM: Enhanced environment variable access for session secret

Replacing direct access to process.env.SESSION_SECRET with getEnv('SESSION_SECRET') improves error handling by ensuring the session secret is defined, which is critical for security.

website/modules/asset/index.js (2)

1-1: LGTM: Proper import of environment utility

The import statement correctly brings in the getEnv utility with an appropriate relative path.


8-8: LGTM: Enhanced environment variable access for NODE_ENV

Replacing direct access to process.env.NODE_ENV with getEnv('NODE_ENV') improves error handling by ensuring the environment variable is defined, which is important for proper environment-specific behavior.

website/e2e/tests/screenshot-test.spec.js (2)

2-2: Good approach centralizing environment variable access

Importing the new getEnv utility function follows best practices by centralizing environment variable access, which improves error handling throughout the application.


10-10:

Details

✅ Verification successful

Verify BASE_URL environment variable is properly set

The change from direct process.env.BASE_URL access to getEnv('BASE_URL') removes any implicit fallback behavior. Make sure this variable is properly set in all environments where tests run.


🏁 Script executed:

#!/bin/bash
# Description: Verify BASE_URL is defined in relevant environment files

echo "Checking for BASE_URL in configuration files:"
rg "BASE_URL" -A 1 -B 1 --glob "!*.js" --glob "!*.md" --glob "!*.json" --glob "!*.lock"

echo -e "\nChecking if other e2e tests might be affected by this change:"
rg "process\.env\.BASE_URL" --glob "*.js" --glob "*.jsx" --glob "*.ts" --glob "*.tsx"

Length of output: 955


Environment variable BASE_URL is properly configured

I’ve verified that:

  • BASE_URL is set in docker-compose.yml (http://localhost:3000) and docker-compose.e2e.yml (http://apostrophe:3000).
  • Playwright’s config falls back to 'http://localhost:3000' when process.env.BASE_URL is unset.
  • website/app.test.js explicitly assigns process.env.BASE_URL for its tests.

No further changes are needed to ensure getEnv('BASE_URL') will always receive a value in existing environments.

website/utils/env.test.js (1)

1-33: Well-structured tests for the env utility

The test file is well-organized with proper test isolation using Jest lifecycle hooks. It thoroughly covers both success and error cases for the getEnv function.

A few observations:

  • Excellent use of beforeEach and afterAll to preserve and restore the original environment
  • Clear test descriptions that explain the expected behavior
  • Good test structure with arrange/act/assert pattern
  • Complete coverage of both successful retrieval and error handling scenarios
website/app.js (3)

3-3: Good approach importing the centralized env utility

Importing the getEnv utility is a positive step toward standardizing environment variable access throughout the application.


8-8: Environment variable validation improved

Replacing direct process.env access with getEnv() calls ensures that missing configuration will fail fast with clear error messages rather than causing subtle issues later.

Also applies to: 17-17, 21-21


28-35: Well-implemented Nunjucks integration for env variables

Making the getEnv function available to templates is an excellent approach that allows consistent environment variable access across both server-side code and templates.

This ensures the same validation and error handling behavior for environment variables accessed from templates.

docker-compose.yml (4)

40-41: Redis and BASE_URL configuration properly added

The addition of REDIS_URI and BASE_URL environment variables aligns with the refactoring to use getEnv() throughout the codebase.


45-54: AWS S3 configuration improved and documented

The S3 settings are now better organized with helpful comments and additional configuration options like APOS_CDN_URL, APOS_S3_STYLE, and APOS_S3_HTTPS.


55-57: Google Cloud Storage configuration added

The addition of Google Cloud Storage settings expands the storage options available to the application.

Note that storing the private key directly in environment variables should be done with caution, especially if using non-secret environment variables elsewhere. Consider using a secret management solution for production environments.


62-62: Redis dependency correctly added

Adding Redis to the depends_on list ensures that the Redis service is started before the Apostrophe service, which is necessary since the application now requires Redis.

website/modules/case-studies-page/views/show.html (1)

43-44: Guard against _industry being undefined

data.piece._industry.length assumes the array is defined. Although Nunjucks is forgiving, an explicit guard improves readability and prevents surprises if the field is ever null:

{% if data.piece._industry and data.piece._industry.length %}

(or use |length which will coerce safely).

website/modules/@apostrophecms/uploadfs/index.js (1)

17-20: Fail-fast when CDN URL is absent

cdn.enabled is hard-coded true, yet getEnv('APOS_CDN_URL') will throw if the variable is missing. Confirm that is desired for local development; otherwise wrap with a conditional or default.

website/modules/case-studies-page/index.js (2)

120-133: Possible mismatch in join-ID property names

The code expects industryIds, stackIds, and caseStudyTypeIds on each study.
Apostrophe creates <fieldName>Ids by stripping the leading underscore from the join field, so for _industry the property is industryIds – this is correct. 👍


5-9:

Details

❓ Verification inconclusive

aposDocId is not a guaranteed field – prefer _id

The helper stores tags by tag.aposDocId, but Apostrophe pieces expose their primary key as _id.
Unless you have custom code adding aposDocId, the lookup will fail and tagMap will be empty, producing zero counts.

-      tagMap[tag.aposDocId] = tag.slug;
+      tagMap[tag._id] = tag.slug;

Please confirm which identifier is actually present in your tag docs.


I’ll verify whether aposDocId actually exists on your tag docs and inspect how tags are defined. Continuing with a few repository searches:


🏁 Script executed:

#!/bin/bash
# Search for any occurrences of aposDocId in the codebase
echo ">>> Searching for aposDocId references"
rg -n "aposDocId"

# Locate the createTagMap implementation
echo -e "\n>>> Locating createTagMap"
rg -n "createTagMap"

# Find where casesTags is sourced or iterated
echo -e "\n>>> Locating casesTags references"
rg -n "casesTags"

# Discover all apostrophe-piece modules to find the tags schema
echo -e "\n>>> Listing all apostrophe-pieces modules"
rg -n "extend: 'apostrophe-pieces'" website/modules

Length of output: 15275


Verify correct identifier for tag mapping

It looks like you’re using tag.aposDocId to key the slug map, but I didn’t find any definition of an aposDocId field on your cases-tags piece schema. Apostrophe pieces expose their primary key as _id by default, so if aposDocId isn’t actually present on each tag doc this lookup will silently fail and your map will be empty.

• File: website/modules/case-studies-page/index.js (around line 8)

Suggested change:

-      tagMap[tag.aposDocId] = tag.slug;
+      tagMap[tag._id] = tag.slug;

Please confirm whether your tag docs include an aposDocId property at runtime or if you should switch to using _id.

docs/Infrastructure.md (10)

1-2: Documentation added: Final Terraform Infrastructure Plan
Great addition—provides a clear high-level overview of the infrastructure components.


3-16: General section is well-structured
Covers regions, environments, domain naming, and tagging conventions concisely.

🧰 Tools
🪛 LanguageTool

[misspelling] ~8-~8: This word is normally spelled as one.
Context: ...tructure**: Modular Terraform setup for multi-environment support * Resource Tags (applied to...

(EN_COMPOUNDS_MULTI_ENVIRONMENT)


19-41: Attachments Bucket details are comprehensive
Lifecycle rules, versioning, encryption, and CORS are all covered.


42-62: Logs Bucket configuration looks solid
Retention and transition policies are clearly defined.


98-114: S3 Bucket Access Policy is detailed and secure
Scoped permissions and resource tagging are clearly defined.


117-143: Amazon ECR section is thorough
Lifecycle policy and scan-on-push settings align with best practices.


146-184: Amazon ECS (Fargate) configuration is complete
Resource allocation, networking, and environment variable definitions are well-documented.


185-202: ALB configuration is clear
HTTPS-only enforcement and ACM integration are correctly specified.


236-257: CloudWatch monitoring and alerts are robust
Log retention and Slack integrations cover operational requirements.


260-298: MongoDB EC2 deployment section is comprehensive
Future HA plan and backup strategy are well-outlined.

IhorMasechko and others added 2 commits May 22, 2025 21:51
- Remove invalid tagCountsMethods property from module top level
- Move tag counting logic into helper utility functions
- Simplify module structure to comply with ApostropheCMS conventions
- Fix linter errors related to function length
coderabbitai[bot]
coderabbitai bot previously approved these changes May 22, 2025
Copy link
Copy Markdown
Contributor

@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

♻️ Duplicate comments (2)
website/modules/case-studies/index.js (1)

197-211: ⚠️ Potential issue

Bug in groupTagsByCategory function

The function attempts to push tag.label to the grouped array, but based on the field projections (lines 48, 63, 78), you're only projecting title, not label. This will result in undefined values being added to the category groups.

-          grouped[category].push(tag.label);
+          grouped[category].push(tag.title);
website/modules/case-studies-page/index.js (1)

104-127: Clean implementation of tag counting methods

The calculateTagCounts method is well-structured and uses the helper functions effectively. It initializes the tag counts object, fetches the necessary data, and processes it to calculate the counts.

The previous issue with calling an undefined logFirstCaseStudy function has been fixed, as noted in past review comments.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff21ccb and ca33acc.

📒 Files selected for processing (3)
  • website/modules/case-studies-page/index.js (2 hunks)
  • website/modules/case-studies-page/views/index.html (4 hunks)
  • website/modules/case-studies/index.js (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • website/modules/case-studies-page/views/index.html
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: e2e-tests
  • GitHub Check: unit-tests
  • GitHub Check: lint
  • GitHub Check: security-scan
🔇 Additional comments (9)
website/modules/case-studies/index.js (7)

12-12: Alias added for programmatic access

Adding an alias for the case study module allows for direct programmatic access via apos.caseStudy in other parts of the codebase, which is good practice for important core modules.


41-55: Field type change from string to relationship enhances data structure

Converting the stack field from a string type to a relationship type with proper projections and relationship configuration improves data structure and enables the categorization feature required by this PR.


56-70: Field type change for case study type enhances categorization

Similar to the stack field, changing the case study type to a relationship field with proper projections allows for better categorization and filtering.


71-85: Industry field upgrade to relationship type

Converting industry to a relationship field completes the set of categorizable fields, maintaining consistency across all tag types.


156-159: Group configuration updated to reflect new field names

The group configuration has been correctly updated to reference the renamed relationship fields.


176-179: Column configuration updated for relationship field

The column configuration has been updated to use the new _stack relationship field name, maintaining the same component for display.


184-192: Filters updated to reflect new relationship fields

Filter configuration has been updated to use the three distinct filter categories (industry, case study type, and stack) instead of a generic tags filter, improving the user filtering experience.

website/modules/case-studies-page/index.js (2)

55-61: Filter structure improved with dedicated category filters

Replacing the generic tags filter with specific filters for industry, stack, and case study type aligns with the PR objective of implementing category-based filtering. The additional options for pieces and piecesFiltersUrl provide proper configuration for the filters.


78-102: Error handling in tag counting initialization

The init method with the beforeIndex hook provides a robust way to calculate tag counts before rendering the page. The error handling ensures that even if tag counting fails, the page will still render with empty tag counts rather than breaking completely.

coderabbitai[bot]
coderabbitai bot previously approved these changes May 22, 2025
yuramax
yuramax previously approved these changes May 23, 2025
Copy link
Copy Markdown
Contributor

@yuramax yuramax left a comment

Choose a reason for hiding this comment

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

Amazing work, @IhorMasechko! 👍

VitalyyP
VitalyyP previously approved these changes May 23, 2025
Copy link
Copy Markdown
Contributor

@VitalyyP VitalyyP left a comment

Choose a reason for hiding this comment

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

LGTM

Anton-88
Anton-88 previously approved these changes May 23, 2025
Copy link
Copy Markdown
Contributor

@Anton-88 Anton-88 left a comment

Choose a reason for hiding this comment

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

LGTM

killev
killev previously approved these changes May 23, 2025
Copy link
Copy Markdown
Collaborator

@killev killev left a comment

Choose a reason for hiding this comment

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

LGTM

@sonarqubecloud
Copy link
Copy Markdown

@IhorMasechko IhorMasechko enabled auto-merge (squash) May 23, 2025 14:58
Copy link
Copy Markdown
Contributor

@yuramax yuramax left a comment

Choose a reason for hiding this comment

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

Looks good👌

Copy link
Copy Markdown
Contributor

@VitalyyP VitalyyP left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Copy Markdown
Contributor

@Anton-88 Anton-88 left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Copy Markdown
Collaborator

@killev killev left a comment

Choose a reason for hiding this comment

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

LGTM

@IhorMasechko IhorMasechko merged commit fb73204 into main May 23, 2025
12 checks passed
@IhorMasechko IhorMasechko deleted the 595-display-created-categories-in-the-sidebar branch May 23, 2025 19:39
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.

5 participants