Skip to content

Comments

CMR-10528: As a CMR Search API user I only want to see active collections in my search results.#2385

Open
daniel-zamora wants to merge 2 commits intomasterfrom
CMR-10528
Open

CMR-10528: As a CMR Search API user I only want to see active collections in my search results.#2385
daniel-zamora wants to merge 2 commits intomasterfrom
CMR-10528

Conversation

@daniel-zamora
Copy link
Contributor

@daniel-zamora daniel-zamora commented Feb 17, 2026

Overview

What is the objective?

Add a default filter that excludes non-operational collections (those with CollectionProgress of PLANNED, DEPRECATED, PREPRINT, or INREVIEW) from collection search results. The filter is controlled by a feature flag and is off by default, allowing it to be enabled without a code deployment.

What are the changes?

  • New ES field (collection-progress-active, boolean): Added to the collection index mapping and populated during indexing. true for ACTIVE, COMPLETE, NOT PROVIDED, SUPERSEDED, and nil; false for PLANNED, DEPRECATED, PREPRINT, INREVIEW.
  • New feature flag (enable-non-operational-collection-filter): Added search-app/src/cmr/search/config.clj. Defaults to false with no behavior change until explicitly enabled.
  • New query parameter (include-non-operational): Accepts true, false, or unset. When the flag is on and the caller does not supply collection-progress or include-non-operational, the search layer synthetically injects include-non-operational=false, which filters to collection-progress-active=true in ES. Passing include-non-operational=true opts out of the filter. Passing an explicit collection-progress value bypasses injection entirely.
  • Integration tests: New search-collection-progress-active-filter deftest covering flag-off, flag-on with no params, explicit opt-in/opt-out, explicit collection-progress bypass, and the conflicting-filter edge case.

What areas of the application does this impact?

  • search-app - parameter parsing, condition conversion, and feature configuration
  • indexer-app - collection ES document population and index mapping
  • system-int-test - collection search integration tests

Required Checklist

  • New and existing unit and int tests pass locally and remotely
  • clj-kondo has been run locally and all errors in changed files are corrected
  • I have commented my code, particularly in hard-to-understand areas
  • I have made changes to the documentation (if necessary)
  • My changes generate no new warnings

Additional Checklist

  • I have removed unnecessary/dead code and imports in files I have changed
  • I have cleaned up integration tests by doing one or more of the following:
    • migrated any are2 tests to are3 in files I have changed
    • de-duped, consolidated, removed dead int tests
    • transformed applicable int tests into unit tests
    • reduced number of system state resets by updating fixtures. Ex) (use-fixtures :each (ingest/reset-fixture {})) to be :once instead of :each

Summary by CodeRabbit

  • New Features

    • Collection search now defaults to excluding non-operational collections (PLANNED, DEPRECATED, PREPRINT, INREVIEW). Use the include-non-operational parameter to include them.
    • Server-side flag available to enable/disable the default non-operational exclusion (default: off).
  • Documentation

    • API docs updated with include-non-operational parameter details and examples.
  • Tests

    • Added comprehensive tests covering collection-progress filtering behavior.

@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

📝 Walkthrough

Walkthrough

Adds a computed collection-progress-active boolean to indexed collections, a feature-flagged include-non-operational search parameter that defaults to excluding non-operational collections when enabled, and test coverage for the new filtering behavior.

Changes

Cohort / File(s) Summary
Collection Indexing
indexer-app/src/cmr/indexer/data/concepts/collection.clj
Extracts collection-progress into a local binding and adds a computed collection-progress-active field used in the indexed document.
Index Schema
indexer-app/src/cmr/indexer/data/index_set.clj
Adds :collection-progress-active boolean mapping to the collection index definition.
Search Configuration & Parameters
search-app/src/cmr/search/config.clj, search-app/src/cmr/search/services/parameters/conversion.clj, search-app/src/cmr/search/services/parameters/parameter_validation.clj
Introduces enable-non-operational-collection-filter config and new include-non-operational parameter; adds parameter-to-condition logic, defaulting behavior when the feature is enabled, and boolean validation for the parameter.
Integration Tests
system-int-test/test/cmr/system_int_test/search/collection/collection_progress_search_test.clj
Adds search-collection-progress-active-filter test validating indexing and search behavior across collection progress values and flag/parameter combinations.
Docs
search-app/docs/api.md
Documents the new include_non_operational collection search parameter and updates the TOC/sample usage.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client
  participant API as Search API
  participant Config as Config Service
  participant QueryParser as Param Parser
  participant ES as Elasticsearch

  Client->>API: GET /collections?include_non_operational=...
  API->>Config: read enable-non-operational-collection-filter
  API->>QueryParser: parse params (apply defaulting using config & aliases)
  QueryParser->>API: parameter conditions (may include collection-progress-active filter)
  API->>ES: search query (includes/excludes non-operational based on condition)
  ES-->>API: results
  API-->>Client: filtered collection results
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • #2361: Prior work adding collection-progress fields and basic search support that this change extends with an active flag and filtering behavior.

Suggested labels

hacktoberfest-accepted

Suggested reviewers

  • eereiter
  • jceaser
  • zimzoom
  • indiejames

Poem

🐰 I found a flag beneath a log,
Quietly hiding non-op catalog,
An active hop, true or false it shows,
Tell me "include" and out it goes,
I nibble indexes while the search wind blows. 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: adding default filtering to exclude non-operational collections from search results.
Description check ✅ Passed The description comprehensively covers all template sections with clear objectives, detailed changes, impacted areas, and completed checklist items.
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
  • Commit unit tests in branch CMR-10528

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

🧹 Nitpick comments (1)
system-int-test/test/cmr/system_int_test/search/collection/collection_progress_search_test.clj (1)

51-52: String literal after deftest name is not a docstring.

deftest doesn't support docstrings the way defn does. The string on line 52 is evaluated and discarded at runtime. If you want to document the test, use a ;; comment instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@system-int-test/test/cmr/system_int_test/search/collection/collection_progress_search_test.clj`
around lines 51 - 52, The string literal placed immediately after the deftest
name in search-collection-progress-active-filter is not a docstring and is being
evaluated/discarded; remove that string literal and replace it with an inline
comment (;; ...) or move the explanatory text outside the deftest, ensuring the
test definition begins with its body (e.g., setup/assertions) and not a quoted
string so the test runs as intended.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@system-int-test/test/cmr/system_int_test/search/collection/collection_progress_search_test.clj`:
- Around line 68-91: The test sets the feature flag via
(cmr.search.config/set-enable-non-operational-collection-filter! true) but only
resets it at the end, so a failing assertion will leave the flag enabled; wrap
the block of assertions between the enable and disable calls in a try/finally so
the flag is always reset: call
(cmr.search.config/set-enable-non-operational-collection-filter! true), run the
tests (the testing forms that call search/find-refs), and in a finally call
(cmr.search.config/set-enable-non-operational-collection-filter! false) to
ensure the flag is cleared even on failure.

---

Nitpick comments:
In
`@system-int-test/test/cmr/system_int_test/search/collection/collection_progress_search_test.clj`:
- Around line 51-52: The string literal placed immediately after the deftest
name in search-collection-progress-active-filter is not a docstring and is being
evaluated/discarded; remove that string literal and replace it with an inline
comment (;; ...) or move the explanatory text outside the deftest, ensuring the
test definition begins with its body (e.g., setup/assertions) and not a quoted
string so the test runs as intended.

Comment on lines +68 to +91
(side/eval-form `(cmr.search.config/set-enable-non-operational-collection-filter! true))

(testing "flag ON, no params - only active collections returned (excludes PLANNED, DEPRECATED, PREPRINT, INREVIEW)"
(d/refs-match? [coll1 coll3 coll5 coll8]
(search/find-refs :collection {})))

(testing "flag ON + include-non-operational=true - all 8 collections returned"
(d/refs-match? [coll1 coll2 coll3 coll4 coll5 coll6 coll7 coll8]
(search/find-refs :collection {:include-non-operational "true"})))

(testing "flag ON + include-non-operational=false - only active collections returned"
(d/refs-match? [coll1 coll3 coll5 coll8]
(search/find-refs :collection {:include-non-operational "false"})))

(testing "flag ON + explicit collection-progress=PLANNED - PLANNED returned (no synthetic filter injected)"
(d/refs-match? [coll2]
(search/find-refs :collection {:collection-progress "PLANNED"})))

(testing "flag ON + collection-progress=PLANNED + include-non-operational=false - empty (PLANNED is non-operational)"
(d/refs-match? []
(search/find-refs :collection {:collection-progress "PLANNED"
:include-non-operational "false"})))

(side/eval-form `(cmr.search.config/set-enable-non-operational-collection-filter! false))))
Copy link

@coderabbitai coderabbitai bot Feb 17, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Feature flag not reset on test failure — can poison subsequent tests.

If any assertion between set-...-filter! true (line 68) and set-...-filter! false (line 91) throws an exception, the flag stays enabled and can cause cascading failures in later tests. Wrap in try/finally:

🛡️ Proposed fix
     (side/eval-form `(cmr.search.config/set-enable-non-operational-collection-filter! true))
 
-    (testing "flag ON, no params - only active collections returned (excludes PLANNED, DEPRECATED, PREPRINT, INREVIEW)"
-      (d/refs-match? [coll1 coll3 coll5 coll8]
-                     (search/find-refs :collection {})))
-
-    (testing "flag ON + include-non-operational=true - all 8 collections returned"
-      (d/refs-match? [coll1 coll2 coll3 coll4 coll5 coll6 coll7 coll8]
-                     (search/find-refs :collection {:include-non-operational "true"})))
-
-    (testing "flag ON + include-non-operational=false - only active collections returned"
-      (d/refs-match? [coll1 coll3 coll5 coll8]
-                     (search/find-refs :collection {:include-non-operational "false"})))
-
-    (testing "flag ON + explicit collection-progress=PLANNED - PLANNED returned (no synthetic filter injected)"
-      (d/refs-match? [coll2]
-                     (search/find-refs :collection {:collection-progress "PLANNED"})))
-
-    (testing "flag ON + collection-progress=PLANNED + include-non-operational=false - empty (PLANNED is non-operational)"
-      (d/refs-match? []
-                     (search/find-refs :collection {:collection-progress "PLANNED"
-                                                    :include-non-operational "false"})))
-
-    (side/eval-form `(cmr.search.config/set-enable-non-operational-collection-filter! false))))
+    (try
+      (testing "flag ON, no params - only active collections returned (excludes PLANNED, DEPRECATED, PREPRINT, INREVIEW)"
+        (d/refs-match? [coll1 coll3 coll5 coll8]
+                       (search/find-refs :collection {})))
+
+      (testing "flag ON + include-non-operational=true - all 8 collections returned"
+        (d/refs-match? [coll1 coll2 coll3 coll4 coll5 coll6 coll7 coll8]
+                       (search/find-refs :collection {:include-non-operational "true"})))
+
+      (testing "flag ON + include-non-operational=false - only active collections returned"
+        (d/refs-match? [coll1 coll3 coll5 coll8]
+                       (search/find-refs :collection {:include-non-operational "false"})))
+
+      (testing "flag ON + explicit collection-progress=PLANNED - PLANNED returned (no synthetic filter injected)"
+        (d/refs-match? [coll2]
+                       (search/find-refs :collection {:collection-progress "PLANNED"})))
+
+      (testing "flag ON + collection-progress=PLANNED + include-non-operational=false - empty (PLANNED is non-operational)"
+        (d/refs-match? []
+                       (search/find-refs :collection {:collection-progress "PLANNED"
+                                                      :include-non-operational "false"})))
+      (finally
+        (side/eval-form `(cmr.search.config/set-enable-non-operational-collection-filter! false))))))
📝 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
(side/eval-form `(cmr.search.config/set-enable-non-operational-collection-filter! true))
(testing "flag ON, no params - only active collections returned (excludes PLANNED, DEPRECATED, PREPRINT, INREVIEW)"
(d/refs-match? [coll1 coll3 coll5 coll8]
(search/find-refs :collection {})))
(testing "flag ON + include-non-operational=true - all 8 collections returned"
(d/refs-match? [coll1 coll2 coll3 coll4 coll5 coll6 coll7 coll8]
(search/find-refs :collection {:include-non-operational "true"})))
(testing "flag ON + include-non-operational=false - only active collections returned"
(d/refs-match? [coll1 coll3 coll5 coll8]
(search/find-refs :collection {:include-non-operational "false"})))
(testing "flag ON + explicit collection-progress=PLANNED - PLANNED returned (no synthetic filter injected)"
(d/refs-match? [coll2]
(search/find-refs :collection {:collection-progress "PLANNED"})))
(testing "flag ON + collection-progress=PLANNED + include-non-operational=false - empty (PLANNED is non-operational)"
(d/refs-match? []
(search/find-refs :collection {:collection-progress "PLANNED"
:include-non-operational "false"})))
(side/eval-form `(cmr.search.config/set-enable-non-operational-collection-filter! false))))
(side/eval-form `(cmr.search.config/set-enable-non-operational-collection-filter! true))
(try
(testing "flag ON, no params - only active collections returned (excludes PLANNED, DEPRECATED, PREPRINT, INREVIEW)"
(d/refs-match? [coll1 coll3 coll5 coll8]
(search/find-refs :collection {})))
(testing "flag ON + include-non-operational=true - all 8 collections returned"
(d/refs-match? [coll1 coll2 coll3 coll4 coll5 coll6 coll7 coll8]
(search/find-refs :collection {:include-non-operational "true"})))
(testing "flag ON + include-non-operational=false - only active collections returned"
(d/refs-match? [coll1 coll3 coll5 coll8]
(search/find-refs :collection {:include-non-operational "false"})))
(testing "flag ON + explicit collection-progress=PLANNED - PLANNED returned (no synthetic filter injected)"
(d/refs-match? [coll2]
(search/find-refs :collection {:collection-progress "PLANNED"})))
(testing "flag ON + collection-progress=PLANNED + include-non-operational=false - empty (PLANNED is non-operational)"
(d/refs-match? []
(search/find-refs :collection {:collection-progress "PLANNED"
:include-non-operational "false"})))
(finally
(side/eval-form `(cmr.search.config/set-enable-non-operational-collection-filter! false))))))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@system-int-test/test/cmr/system_int_test/search/collection/collection_progress_search_test.clj`
around lines 68 - 91, The test sets the feature flag via
(cmr.search.config/set-enable-non-operational-collection-filter! true) but only
resets it at the end, so a failing assertion will leave the flag enabled; wrap
the block of assertions between the enable and disable calls in a try/finally so
the flag is always reset: call
(cmr.search.config/set-enable-non-operational-collection-filter! true), run the
tests (the testing forms that call search/find-refs), and in a finally call
(cmr.search.config/set-enable-non-operational-collection-filter! false) to
ensure the flag is cleared even on failure.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

other tests use this pattern, and if any exception or error is thrown by the apps its handled gracefull by the apps and the dev system itself remains running

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for similar patterns where side/eval-form is used to toggle feature flags
# Look for patterns where flags are set and then reset at the end without try/finally
rg -n -C5 'side/eval-form.*set-enable' --type=clj

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Also search for any try/finally patterns in test files to see if they're used
rg -n -C3 '\(try\b' system-int-test/test/ --type=clj | head -100

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Find other tests that toggle configuration or state without cleanup protection
rg -n -A10 'side/eval-form' system-int-test/test/ --type=clj | head -150

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Search for similar patterns where side/eval-form is used to toggle feature flags
rg -n -C5 'side/eval-form' -g '*.clj' system-int-test/test/ | head -200

Length of output: 28251


🏁 Script executed:

#!/bin/bash
# Check if try/finally is commonly used in test files
rg -n -C3 '\(try\b' -g '*.clj' system-int-test/test/ | head -100

Length of output: 8313


@daniel-zamora, thank you for the clarification! I can confirm that pattern exists throughout the test suite. I found numerous examples where side/eval-form is used to toggle configuration without try/finally blocks, including tests like multipolygon-shapefile-granule-counts-test, subscription-count-exceeds-limit-test, and others.

Given that this is an established pattern in the codebase and you've confirmed the exception handling works gracefully in your environment, I understand the current approach is intentional and appropriate for your testing infrastructure. I appreciate you taking the time to explain the context!


🧠 Learnings used
Learnt from: daniel-zamora
Repo: nasa/Common-Metadata-Repository PR: 2361
File: search-app/src/cmr/search/services/parameters/conversion.clj:473-487
Timestamp: 2025-12-30T13:06:11.311Z
Learning: The collection-progress field in CMR collections is a single-valued enum. A collection can only have one progress value at a time, so AND operations on collection-progress parameters don't make logical sense even though the implementation supports them for consistency with other parameter handlers.

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 `@search-app/docs/api.md`:
- Line 2386: Replace the indented code block containing the curl example ("curl
\"%CMR-ENDPOINT%/collections?include_non_operational=true\"") with a fenced code
block labeled with the language (e.g., bash) to satisfy markdownlint MD046;
locate the curl snippet in the API docs and wrap it with triple backticks and
the language tag (bash) so the snippet is a proper fenced block.

* `false` — restrict results to operational collections only (`ACTIVE`, `COMPLETE`, `NOT PROVIDED`, `SUPERSEDED`, and collections with no `CollectionProgress` set)
* `unset` (default) — behavior is determined by the server-side feature flag

curl "%CMR-ENDPOINT%/collections?include_non_operational=true"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use fenced code block to satisfy markdownlint (MD046).

Line 2386 is an indented code block; markdownlint expects fenced blocks.

📌 Suggested fix
-      curl "%CMR-ENDPOINT%/collections?include_non_operational=true"
+```bash
+curl "%CMR-ENDPOINT%/collections?include_non_operational=true"
+```
📝 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
curl "%CMR-ENDPOINT%/collections?include_non_operational=true"
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 2386-2386: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@search-app/docs/api.md` at line 2386, Replace the indented code block
containing the curl example ("curl
\"%CMR-ENDPOINT%/collections?include_non_operational=true\"") with a fenced code
block labeled with the language (e.g., bash) to satisfy markdownlint MD046;
locate the curl snippet in the API docs and wrap it with triple backticks and
the language tag (bash) so the snippet is a proper fenced block.

@codecov-commenter
Copy link

codecov-commenter commented Feb 17, 2026

Codecov Report

❌ Patch coverage is 30.00000% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 57.93%. Comparing base (594b8ba) to head (d75208f).

Files with missing lines Patch % Lines
.../src/cmr/search/services/parameters/conversion.clj 27.27% 7 Missing and 1 partial ⚠️
...r-app/src/cmr/indexer/data/concepts/collection.clj 0.00% 5 Missing ⚠️
search-app/src/cmr/search/config.clj 66.66% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2385      +/-   ##
==========================================
- Coverage   57.94%   57.93%   -0.01%     
==========================================
  Files        1066     1067       +1     
  Lines       73211    73228      +17     
  Branches     2121     2119       -2     
==========================================
+ Hits        42420    42424       +4     
- Misses      28818    28828      +10     
- Partials     1973     1976       +3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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.

2 participants