Skip to content

EDSC-4662: As a user, I would like to set reprojection and know which parameters are still valid#2055

Merged
mandyparson merged 10 commits into
feature/EDSC-4638from
EDSC-4662
Jun 23, 2026
Merged

EDSC-4662: As a user, I would like to set reprojection and know which parameters are still valid#2055
mandyparson merged 10 commits into
feature/EDSC-4638from
EDSC-4662

Conversation

@mandyparson

@mandyparson mandyparson commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Overview

What is the feature?

In addition to the AC for this ticket, this PR aims to also ensure that all other elements of this element are working as expected. The functionality of other access methods should have stayed the same while changes to Harmony were made.

What is the Solution?

A lot of refactoring.

What areas of the application does this impact?

AccessMethods, createProjectSlice, buildHarmony, getDerivedHarmonyState and associated utilities. Many of which I was able to cut after these changes had been made.

Please note that some of these files and associated tests have been converted to typescript for better performance and readability.

Testing

Reproduction steps

Harmony Documentation so you can find a service to work with: https://harmony.earthdata.nasa.gov/docs
Harmony link to capabilities document used to create any HarmonyAccessMethod: https://harmony.earthdata.nasa.gov/capabilities?collectionId=&version=3

Some collections I've been using:

C3685896287-LARC_CLOUD: Has interpolation methods | sds/swath-projector
https://harmony.earthdata.nasa.gov/capabilities?collectionId=C3685896287-LARC_CLOUD&version=3

C2949811996-POCLOUD: Has supportedProjections | HyBIG >> Mocked with json object below
https://harmony.earthdata.nasa.gov/capabilities?collectionId=C2949811996-POCLOUD&version=3

C2723754847-GES_DISC: No Concatenation and Variables without Science Keywords

C1453188197-GES_DISC: Has variables with scienceKeywords

C2205121315-POCLOUD: Has concatenation

C4054955340-GES_DISC - General
https://harmony.earthdata.nasa.gov/capabilities?collectionId=C4054955340-GES_DISC&version=3

C3685668680-LARC_CLOUD - Selecting PNG disables spatial and temporal (also has reprojection)
https://harmony.earthdata.nasa.gov/capabilities?collectionId=C3685668680-LARC_CLOUD&version=3

C3300834719-OB_CLOUD - No services but has type Harmony
https://harmony.earthdata.nasa.gov/capabilities?collectionId=C3300834719-OB_CLOUD&version=3

C1623882456-LPDAAC_ECS: ESI

C2799438271-POCLOUD: SWODLR

UAT C1224234386-E2E_18_4 - Opendap

For testing, select any collection for your project (preferably multiple) and start messing around with the form. See if anything breaks or doesn't work as it should.

Attachments

Please include relevant screenshots or files that would be helpful in reviewing and verifying this change.

Checklist

  • I have added automated tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced Harmony access method discovery and configuration with improved capability detection.
    • Reorganized access method customization interface for clearer subsetting and format options.
    • Updated variable selection interface and identification handling.
  • Bug Fixes

    • Improved output format availability and projection option handling.
  • Refactor

    • Restructured access method state management and derivation logic.
    • Refreshed output format mapping and display labels.

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Lost in the diff? Review this PR in Change Stack to follow the change map from intent to exact ranges.

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 81542755-a17e-4fc8-b372-7c411673735f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR refactors how Harmony access-method capabilities are computed and applied. It introduces a getDerivedHarmonyState system that dynamically filters services and computes availability based on user selections, replaces several utility functions with a capabilities-driven approach, migrates key utilities from JavaScript to TypeScript, refactors the AccessMethod component to use derived state flags instead of local React state, integrates Harmony capabilities fetching into the Zustand store, and updates retrieval and configuration handling accordingly.

Changes

Harmony Capabilities Infrastructure and Refactoring

Layer / File(s) Summary
Harmony types and data models
static/src/js/util/getDerivedHarmonyState/getDerivedHarmonyState.ts, static/src/js/types/sharedTypes.ts
TypeScript interfaces and types define Harmony capabilities documents, user selections, derived state with enable/disable flags, output format/projection availability maps, subsetting/reprojection support, science keywords, and variables.
getDerivedHarmonyState implementation
static/src/js/util/getDerivedHarmonyState/getDerivedHarmonyState.ts, static/src/js/util/getDerivedHarmonyState/__tests__/*
Core function filters Harmony services based on user selections, computes disabled capabilities and availability maps for formats/projections, and returns derived state; comprehensive test coverage validates capability recalculation across spatial/temporal/variable/format/projection selections.
Harmony capabilities request handling
static/src/js/util/request/harmonyCapabilitiesDocumentRequest.ts, static/src/js/util/request/request.ts, static/src/js/util/request/__tests__/*
HarmonyCapabilitiesDocumentRequest class fetches Harmony capabilities via authenticated GET requests; Request.getSearch() method enables query-parameter-based searches.
Variable ID determination
static/src/js/util/determineVariableId.ts, static/src/js/util/__tests__/determineVariableId.test.ts
determineVariableId helper extracts stable identifiers from both UMM-S variables (via conceptId) and Harmony variables (via href parsing); used throughout variable mapping and selection logic.
getVariables migration
static/src/js/util/accessMethods/getVariables.ts, static/src/js/util/accessMethods/__tests__/getVariables.test.js
Replaces JavaScript implementation with TypeScript version that normalizes Harmony/CMR variable responses into hierarchy/keyword mappings and a variables dictionary keyed by determined ID.
buildAccessMethods and buildHarmony refactor
static/src/js/util/accessMethods/buildAccessMethods.ts, static/src/js/util/accessMethods/buildAccessMethods/buildHarmony.ts, static/src/js/util/accessMethods/__tests__/buildAccessMethods.test.ts, static/src/js/util/accessMethods/buildAccessMethods/__tests__/buildHarmony.test.ts
Migrates buildAccessMethods from JavaScript to TypeScript with Harmony capabilities integration; new buildHarmony factory converts capabilities and selections into HarmonyAccessMethod objects with derived state; extensive test suite mocks all builders and verifies correct delegation and Harmony integration.
buildOpendap output format updates
static/src/js/util/accessMethods/buildAccessMethods/buildOpendap.js, static/src/js/util/accessMethods/buildAccessMethods/__tests__/buildOpendap.test.js
Uses new ousFormatMapping and structures supported output formats as { name, mimeType } objects instead of strings; test expectations updated accordingly.
Output format mapping replacement
sharedUtils/outputFormatMaps.js
Replaces old harmonyFormatMapping (mimetypes) with new ousFormatMapping (file extensions for NETCDF-3, NETCDF-4, BINARY, ASCII).
Removed obsolete utilities
static/src/js/util/accessMethods/supportsConcatenation.js, static/src/js/util/accessMethods/supportsTemporalSubsetting.js, static/src/js/util/accessMethods/supportsBoundingBoxSubsetting.js, static/src/js/util/accessMethods/supportsShapefileSubsetting.js, static/src/js/util/accessMethods/defaultConcatenation.js, related test files
Old utility functions and tests removed; capability checking is now integrated into derived-state computation.
Test mocks reorganization
static/src/js/components/AccessMethod/__tests__/__mocks__/mocks.ts, static/src/js/util/accessMethods/__tests__/mocks.js
Moves AccessMethod test mocks to new __mocks__/mocks.ts; renames OPeNDAP-specific fixtures (e.g., variablesResponseopendapVariablesResponse); adds new Harmony-specific mock constants for variables, keyword mappings, and responses.
AccessMethod component refactoring
static/src/js/components/AccessMethod/AccessMethod.jsx, static/src/js/components/AccessMethod/__tests__/AccessMethod.test.jsx
Component destructures derived selection/availability flags (is*Selected, is*Disabled, availability maps) instead of maintaining local state; simplifies toggle handlers to call onUpdateAccessMethod; rebuilds radio options via useMemo; revises output format/projection/subsetting sections to use availability maps; extensive test updates validate derived flag behavior across OPeNDAP, SWODLR, and Harmony.
ProjectPanels variable selection update
static/src/js/components/ProjectPanels/ProjectPanels.jsx, static/src/js/components/ProjectPanels/__tests__/ProjectPanels.test.jsx
Tracks selected variable by selectedVariableId (concept ID) via determineVariableId; derives detailedVariable from metadata via concept-ID lookup; wires new onUpdateHarmonySelection prop; test updates adjust variable fixtures and assertions to match new shape.
Zustand store type updates
static/src/js/zustand/types.ts
Updates HarmonyAccessMethod and OpendapAccessMethod types to use derived Harmony state, new mapping types, and adds updateHarmonySelection action.
Harmony capabilities fetching
static/src/js/zustand/slices/createProjectSlice.ts, static/src/js/zustand/slices/__tests__/createProjectSlice.test.ts
getProjectCollections fetches Harmony capabilities per collection via HarmonyCapabilitiesDocumentRequest, passes documents to buildAccessMethods; new updateHarmonySelection action recomputes derived state and synchronizes enable/disable flags; extensive test updates mock capabilities requests and verify Harmony access-method construction.
Retrieval and hook updates
static/src/js/hooks/useCreateRetrieval.ts, static/src/js/util/retrievals.js, static/src/js/util/__tests__/retrievals.test.js
useCreateRetrieval simplifies Harmony service-name handling by assigning "Harmony" statically; retrievals.js updates whitelisted fields from defaultConcatenation to enableConcatenateDownload, enableSpatialSubsetting, enableTemporalSubsetting; test mocks updated to new Harmony structure.
Configuration and e2e tests
static.config.json, tests/e2e/history/history.spec.js, tests/e2e/page_titles/page_titles.spec.js, retrieval tests
Adds harmonyHost to environment configs; stubs Harmony capabilities endpoint in e2e tests; updates retrieval test fixtures with new Harmony structures.

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • nasa/earthdata-search#2051: Both PRs update Harmony output-format plumbing together—replacing harmonyFormatMapping with ousFormatMapping and refactoring output/projection option generation in AccessMethod component.

Suggested reviewers

  • trevorlang
  • eudoroolivares2016
  • macrouch

Poem

🐰 Harmonious refactoring hops through the code,
Capabilities now derived down the state-driven road,
No more local echoes of enable/disable flags,
TypeScript types replace JavaScript's scattered tags,
Zustand dances with Harmony, together they glow!

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch EDSC-4662

@mandyparson mandyparson changed the base branch from main to feature/EDSC-4638 June 5, 2026 21:13
@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown

Bundle Size Comparison

Full build details

> earthdata-search@1.0.0 build
> vite build

vite v6.4.2 building for production...
transforming...
✓ 3945 modules transformed.
rendering chunks...
computing gzip size...
static/dist/assets/logo-DBK9nthR.webp                                                      0.97 kB
static/dist/assets/logo-CZz0VEVL.webp                                                      1.14 kB
static/dist/assets/logo-Cycj4Zl7.webp                                                      1.33 kB
static/dist/assets/logo-U_2b2F1P.webp                                                      1.37 kB
static/dist/assets/logo-DrQyzv8O.webp                                                      1.54 kB
static/dist/assets/logo-BtXPiD8D.webp                                                      1.81 kB
static/dist/assets/logo-CBoC93bb.webp                                                      2.16 kB
static/dist/assets/logo-DjzD1LMz.webp                                                      2.39 kB
static/dist/assets/orange-bars-circle-D7AglAGM.webp                                        2.77 kB
static/dist/assets/logo-mTUaHBfB.webp                                                      3.11 kB
static/dist/assets/blue-bars-circle-BoIdEwIS.webp                                          3.67 kB
static/dist/assets/logo-BRHnty9h.webp                                                      4.09 kB
static/dist/assets/cryosphere-icon-DSWEvPIt.svg                                            4.11 kB │ gzip:   1.64 kB
static/dist/assets/logo-m8iUFUyE.webp                                                      4.26 kB
static/dist/assets/logo-BI7QOwpp.webp                                                      4.72 kB
static/dist/assets/logo-CjKnSDvE.webp                                                      7.06 kB
static/dist/index.html                                                                    13.22 kB │ gzip:   3.09 kB
static/dist/assets/plate_carree_earth_scaled-RfqGhO39.png                                 18.14 kB
static/dist/assets/tour-video-thumbnail-BxsZeqvq.webp                                     18.84 kB
static/dist/assets/plate_carree_earth_scaled@2x-BIj0Wstc.png                              45.03 kB
static/dist/assets/image-unavailable-CcXgOtQr.svg                                         52.20 kB │ gzip:  20.77 kB
static/dist/assets/earthdata-search-og-image-Cs5RILJ1.webp                                92.82 kB
static/dist/assets/MODIS-Terra-Swirling-Clouds-In-Atlantic-800x600@2x-DdSR_KXb.webp      132.70 kB
static/dist/assets/MODIS-Terra-Swirling-Clouds-In-Atlantic-2560x1440@2x-CZbMhKXi.webp    224.17 kB
static/dist/assets/MODIS-Terra-Swirling-Clouds-In-Atlantic-800x600@2x-DFWWupvf.webp      381.18 kB
static/dist/assets/MODIS-Terra-Swirling-Clouds-In-Atlantic-2560x1440@2x-BwDp59vN.webp    464.37 kB
static/dist/assets/MODIS-Terra-Swirling-Clouds-In-Atlantic-2560x1440@2x-CeYlmMCD.webp    737.44 kB
static/dist/assets/MODIS-Terra-Swirling-Clouds-In-Atlantic-2560x1440@2x-tQ4uTIyn.webp  1,255.87 kB
static/dist/assets/MODIS-Terra-Swirling-Clouds-In-Atlantic-2560x1440@2x-BqbXRJmQ.webp  1,516.81 kB
static/dist/assets/AdminRetrieval-nNSujMnn.css                                             0.06 kB │ gzip:   0.08 kB
static/dist/assets/AdminPreferencesMetrics-GkGryAtC.css                                    0.10 kB │ gzip:   0.11 kB
static/dist/assets/ChunkedOrderModal-DIli9vpQ.css                                          0.11 kB │ gzip:   0.10 kB
static/dist/assets/TooManyPointsModal-D8ufPyjz.css                                         0.14 kB │ gzip:   0.12 kB
static/dist/assets/AdminRetrievalsMetrics-CT7bgXZt.css                                     0.22 kB │ gzip:   0.13 kB
static/dist/assets/AdminPage-D19229XC.css                                                  0.23 kB │ gzip:   0.16 kB
static/dist/assets/ContactInfo-C7Pr11wu.css                                                0.28 kB │ gzip:   0.18 kB
static/dist/assets/useDeleteSubscription-YPccXDF3.css                                      0.46 kB │ gzip:   0.23 kB
static/dist/assets/ShapefileDropzoneContainer-BG7JE7Dj.css                                 0.53 kB │ gzip:   0.26 kB
static/dist/assets/GranuleFiltersContainer-CfwvM6Mp.css                                    0.59 kB │ gzip:   0.27 kB
static/dist/assets/ShapefileUploadModal-CMzmPyoZ.css                                       0.66 kB │ gzip:   0.32 kB
static/dist/assets/Preferences-DGkx-xES.css                                                0.68 kB │ gzip:   0.24 kB
static/dist/assets/Skeleton-DtMOdDsM.css                                                   0.88 kB │ gzip:   0.34 kB
static/dist/assets/TextWindowActions-aNefZur7.css                                          0.91 kB │ gzip:   0.38 kB
static/dist/assets/RelatedCollection-BaShQZdF.css                                          0.93 kB │ gzip:   0.40 kB
static/dist/assets/CollectionDetailsHighlights-CHDYEfjF.css                                1.24 kB │ gzip:   0.44 kB
static/dist/assets/GranuleResultsHighlights-BIY0O9bb.css                                   1.24 kB │ gzip:   0.41 kB
static/dist/assets/Subscriptions-CYrzsucf.css                                              1.26 kB │ gzip:   0.40 kB
static/dist/assets/EDSCModalContainer-D_JXXJBf.css                                         1.77 kB │ gzip:   0.59 kB
static/dist/assets/AdminProjects-CZeT2VXI.css                                              1.98 kB │ gzip:   0.46 kB
static/dist/assets/AdminRetrievals-DIdJ2AD-.css                                            2.02 kB │ gzip:   0.46 kB
static/dist/assets/DownloadHistory-C2TrZbdc.css                                            2.05 kB │ gzip:   0.47 kB
static/dist/assets/DeprecatedParameterModal-nutVvbLm.css                                   2.12 kB │ gzip:   0.93 kB
static/dist/assets/Projects-5m-RoZO0.css                                                   2.13 kB │ gzip:   0.51 kB
static/dist/assets/EchoForm-VVUehB8V.css                                                   3.47 kB │ gzip:   1.06 kB
static/dist/assets/SearchTour-AJompJPK.css                                                 4.82 kB │ gzip:   1.22 kB
static/dist/assets/index-BOElThXL.css                                                      6.47 kB │ gzip:   1.24 kB
static/dist/assets/OrderStatus-qyLNmnfS.css                                                8.49 kB │ gzip:   1.93 kB
static/dist/assets/createSpatialDisplay-CiT4b-tQ.css                                      10.62 kB │ gzip:   2.41 kB
static/dist/assets/MapContainer-GerI1QoD.css                                              17.00 kB │ gzip:   5.89 kB
static/dist/assets/Project-RbLZgqcX.css                                                   20.04 kB │ gzip:   4.07 kB
static/dist/assets/Search-DWJQ45Qd.css                                                    69.43 kB │ gzip:  10.66 kB
static/dist/assets/index-DvsUNaze.css                                                    339.58 kB │ gzip: 109.39 kB
static/dist/assets/logo-BHeuky8i.js                                                        0.06 kB │ gzip:   0.08 kB
static/dist/assets/logo-C2M9lK3U.js                                                        0.06 kB │ gzip:   0.08 kB
static/dist/assets/logo-BbJQ0fWs.js                                                        0.06 kB │ gzip:   0.08 kB
static/dist/assets/logo-CW6PQqmQ.js                                                        0.06 kB │ gzip:   0.08 kB
static/dist/assets/logo-CM8gX-og.js                                                        0.06 kB │ gzip:   0.08 kB
static/dist/assets/logo-CGJHAKVD.js                                                        0.06 kB │ gzip:   0.08 kB
static/dist/assets/logo-DOLER09h.js                                                        0.06 kB │ gzip:   0.08 kB
static/dist/assets/logo-C6Ht_tBx.js                                                        0.06 kB │ gzip:   0.08 kB
static/dist/assets/logo-J7t-4CBp.js                                                        0.06 kB │ gzip:   0.08 kB
static/dist/assets/logo-CxvnVTz2.js                                                        0.06 kB │ gzip:   0.08 kB
static/dist/assets/logo-C9HDQHSy.js                                                        0.06 kB │ gzip:   0.08 kB
static/dist/assets/logo-IHV3gtAZ.js                                                        0.06 kB │ gzip:   0.08 kB
static/dist/assets/logo-Dedvhxzp.js                                                        0.06 kB │ gzip:   0.08 kB
static/dist/assets/logo--kH-hKRS.js                                                        0.06 kB │ gzip:   0.08 kB
static/dist/assets/logo-Z4bYAKVs.js                                                        0.06 kB │ gzip:   0.08 kB
static/dist/assets/logo-nOuRrv94.js                                                        0.06 kB │ gzip:   0.08 kB
static/dist/assets/logo-DIxV4RPX.js                                                        0.06 kB │ gzip:   0.08 kB
static/dist/assets/logo-uABdcgsF.js                                                        0.06 kB │ gzip:   0.08 kB
static/dist/assets/pluralize-49vSaAHF.js                                                   0.08 kB │ gzip:   0.09 kB
static/dist/assets/accessMethodTypes-CY5JnOnd.js                                           0.13 kB │ gzip:   0.14 kB
static/dist/assets/commafy-BfQYQOA4.js                                                     0.14 kB │ gzip:   0.14 kB
static/dist/assets/requestDebounceDuration-B92It3cW.js                                     0.16 kB │ gzip:   0.11 kB
static/dist/assets/subscriptions-DhIht9om.js                                               0.41 kB │ gzip:   0.21 kB
static/dist/assets/Table-BePD1o3U.js                                                       0.60 kB │ gzip:   0.38 kB
static/dist/assets/humanizedQueryValueFormatters-whb_SxUW.js                               1.00 kB │ gzip:   0.54 kB
static/dist/assets/TooManyPointsModal-Ct-RvGf2.js                                          1.10 kB │ gzip:   0.58 kB
static/dist/assets/DownloadsLayout-aosHO39-.js                                             1.12 kB │ gzip:   0.36 kB
static/dist/assets/metricsDataAccess-B0dv9eSs.js                                           1.26 kB │ gzip:   0.35 kB
static/dist/assets/AdminLayout-B3i993ML.js                                                 1.45 kB │ gzip:   0.48 kB
static/dist/assets/index-DNiUIiJM.js                                                       1.64 kB │ gzip:   0.94 kB
static/dist/assets/useLazyQuery-DmjkaZ9Q.js                                                1.70 kB │ gzip:   0.89 kB
static/dist/assets/DefinitionList-BMbFY7iR.js                                              1.85 kB │ gzip:   0.55 kB
static/dist/assets/AdminPage-B5k4aONi.js                                                   1.98 kB │ gzip:   0.51 kB
static/dist/assets/Skeleton-CHnTWDyr.js                                                    2.23 kB │ gzip:   0.71 kB
static/dist/assets/KeyboardShortcutsModal-Djb8T6EN.js                                      2.36 kB │ gzip:   0.85 kB
static/dist/assets/AdminIndex-Ca35Ev-m.js                                                  2.69 kB │ gzip:   0.86 kB
static/dist/assets/useCreateRetrieval-C7apuJrc.js                                          3.26 kB │ gzip:   1.59 kB
static/dist/assets/DeprecatedParameterModal-BwXsKSQ6.js                                    3.42 kB │ gzip:   1.30 kB
static/dist/assets/AdminProject-CHQgv6dp.js                                                3.66 kB │ gzip:   1.03 kB
static/dist/assets/EditSubscriptionModal-7qeVSrmZ.js                                       3.73 kB │ gzip:   1.27 kB
static/dist/assets/ChunkedOrderModal-CPR5j4r_.js                                           4.14 kB │ gzip:   1.36 kB
static/dist/assets/ShapefileUploadModal-AjLGQBJR.js                                        4.97 kB │ gzip:   1.00 kB
static/dist/assets/AboutCSDAModal-DKzOg8Sh.js                                              5.18 kB │ gzip:   1.19 kB
static/dist/assets/AdminPreferencesMetrics-BbnMvPNe.js                                     5.66 kB │ gzip:   1.05 kB
static/dist/assets/EarthdataDownloadRedirect-B1yicf3w.js                                   5.98 kB │ gzip:   1.11 kB
static/dist/assets/EDSCModalContainer-DlTInudJ.js                                          7.38 kB │ gzip:   1.72 kB
static/dist/assets/GranuleResultsHighlights-BfCmcA6d.js                                    7.92 kB │ gzip:   1.61 kB
static/dist/assets/DownloadHistory-Chf0FulJ.js                                             7.99 kB │ gzip:   1.70 kB
static/dist/assets/CollectionDetailsHighlights-3TsiyBtV.js                                 9.97 kB │ gzip:   1.27 kB
static/dist/assets/useDeleteSubscription-vKhwncaq.js                                      10.57 kB │ gzip:   2.72 kB
static/dist/assets/Modal-DjyOaFzA.js                                                      10.74 kB │ gzip:   4.05 kB
static/dist/assets/AdminRetrieval-X2Rb7jIK.js                                             10.84 kB │ gzip:   2.13 kB
static/dist/assets/AdminProjects-CWVdjxcg.js                                              10.85 kB │ gzip:   1.93 kB
static/dist/assets/RelatedCollection-DwfDkinQ.js                                          11.09 kB │ gzip:   4.13 kB
static/dist/assets/AboutCwicModal-VX1QSoIO.js                                             11.28 kB │ gzip:   2.26 kB
static/dist/assets/AdminRetrievals-ChQCKsJA.js                                            11.43 kB │ gzip:   1.99 kB
static/dist/assets/Projects-DXcOkPJp.js                                                   11.66 kB │ gzip:   2.15 kB
static/dist/assets/Subscriptions-Cxxt3LEY.js                                              13.24 kB │ gzip:   2.15 kB
static/dist/assets/ContactInfo-ClsOqPYU.js                                                13.41 kB │ gzip:   1.93 kB
static/dist/assets/AdminRetrievalsMetrics-Dt4gr5bM.js                                     15.01 kB │ gzip:   2.08 kB
static/dist/assets/index-uhIDYW5z.js                                                      35.34 kB │ gzip:  12.82 kB
static/dist/assets/GranuleFiltersContainer-BLPjaVZz.js                                    39.70 kB │ gzip:   6.76 kB
static/dist/assets/styles-BOUykk1C.js                                                     60.27 kB │ gzip:  18.66 kB
static/dist/assets/moment-BAtM24Ew.js                                                     60.92 kB │ gzip:  19.77 kB
static/dist/assets/createSpatialDisplay-BSTDIuic.js                                       81.69 kB │ gzip:  18.54 kB
static/dist/assets/lodash-8CeAvfMs.js                                                     92.24 kB │ gzip:  31.91 kB
static/dist/assets/OrderStatus-DcLmgTo7.js                                               108.80 kB │ gzip:  20.22 kB
static/dist/assets/EchoForm-Dn0EH-fA.js                                                  108.86 kB │ gzip:  33.76 kB
static/dist/assets/ShapefileDropzoneContainer-gRuS3pWG.js                                119.75 kB │ gzip:  40.14 kB
static/dist/assets/SearchTour-AIOKNoAS.js                                                139.43 kB │ gzip:  37.01 kB
static/dist/assets/edscUtils-BtuhboRZ.js                                                 141.73 kB │ gzip:  46.95 kB
static/dist/assets/Project-4Qngu5OE.js                                                   355.01 kB │ gzip: 118.26 kB
static/dist/assets/Preferences-DZYf187A.js                                               369.31 kB │ gzip: 115.78 kB
static/dist/assets/react-BTzb2ItR.js                                                     409.72 kB │ gzip: 127.89 kB
static/dist/assets/Search-DHk-LXJM.js                                                    660.96 kB │ gzip: 155.54 kB
static/dist/assets/index-8HJJwtPg.js                                                   1,133.35 kB │ gzip: 351.28 kB
static/dist/assets/MapContainer-8-7h9e-R.js                                            1,332.05 kB │ gzip: 270.47 kB
✓ built in 37.87s

The full bundle is smaller than main by -104.62 kB. 🎉

The index.js is smaller than main by -2.97 kB. 🎉

Run npx vite-bundle-visualizer to review the bundle in more detail.

File Main Branch Diff
Total 11081.91 kB 10977.29 kB -104.62 kB
index.js 1173.3 kB 1170.33 kB -2.97 kB
index.js (gzip) 366.07 kB 365.04 kB -1.03 kB
index.css 346.05 kB 346.05 kB 0 kB
index.css (gzip) 110.63 kB 110.63 kB 0 kB
Number of files 139 139 0
Build Time 37.97 s 37.87 s -0.1 s

@codecov

codecov Bot commented Jun 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.23372% with 2 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (feature/EDSC-4638@4cd834d). Learn more about missing BASE report.

Files with missing lines Patch % Lines
...ic/src/js/util/accessMethods/buildAccessMethods.ts 98.43% 1 Missing ⚠️
static/src/js/zustand/slices/createProjectSlice.ts 97.05% 1 Missing ⚠️
Additional details and impacted files
@@                 Coverage Diff                  @@
##             feature/EDSC-4638    #2055   +/-   ##
====================================================
  Coverage                     ?   93.54%           
====================================================
  Files                        ?      752           
  Lines                        ?    17483           
  Branches                     ?     4887           
====================================================
  Hits                         ?    16355           
  Misses                       ?     1017           
  Partials                     ?      111           

☔ View full report in Codecov by Harness.
📢 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.

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

Actionable comments posted: 10

Caution

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

⚠️ Outside diff range comments (2)
static/src/js/components/AccessMethod/AccessMethod.jsx (1)

118-140: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix useEffect dependency array to prevent stale closure bugs.

The effect uses isTemporalSubsettingSelected, isRecurring, onUpdateAccessMethod, metadata.conceptId, and selectedAccessMethod, but only lists [projectCollection] in the dependency array. This can cause the effect to use stale values if those variables change without projectCollection changing.

Additionally, the auto-disable logic for recurring temporal subsetting should probably be in a separate useEffect that specifically watches isRecurring and isTemporalSubsettingSelected, rather than being coupled to granule list rebuilding.

🔧 Recommended fix

Split into two separate effects:

  useEffect(() => {
    if (addedGranuleIds.length > 0) {
      granulesToDisplay = addedGranuleIds
    } else {
      granulesToDisplay = granulesAllIds
    }

    // Build the list of granules
    const granuleListObj = granulesToDisplay.map((id) => granulesMetadata[id])
    setGranuleList(granuleListObj)
+  }, [projectCollection])
+
+  // Disable temporal subsetting if the user has a recurring date selected
+  useEffect(() => {
-    // Disable temporal subsetting if the user has a recurring date selected
    if (isTemporalSubsettingSelected && isRecurring) {
      onUpdateAccessMethod({
        collectionId: metadata.conceptId,
        method: {
          [selectedAccessMethod]: {
            enableTemporalSubsetting: false
          }
        }
      })
    }
-  }, [projectCollection])
+  }, [isTemporalSubsettingSelected, isRecurring, onUpdateAccessMethod, metadata.conceptId, selectedAccessMethod])
🤖 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 `@static/src/js/components/AccessMethod/AccessMethod.jsx` around lines 118 -
140, The current useEffect in AccessMethod.jsx that rebuilds granuleList also
references isTemporalSubsettingSelected, isRecurring, onUpdateAccessMethod,
metadata.conceptId, and selectedAccessMethod but only lists [projectCollection],
causing stale closures; split into two effects: keep the granule-list building
effect (useEffect that maps addedGranuleIds/granulesAllIds using
granulesMetadata and calls setGranuleList) with dependencies [projectCollection,
addedGranuleIds, granulesAllIds, granulesMetadata, setGranuleList], and create a
separate effect for the auto-disable logic that watches
[isTemporalSubsettingSelected, isRecurring, onUpdateAccessMethod,
metadata.conceptId, selectedAccessMethod] and calls onUpdateAccessMethod({
collectionId: metadata.conceptId, method: { [selectedAccessMethod]: {
enableTemporalSubsetting: false } } }) only when isTemporalSubsettingSelected &&
isRecurring to avoid stale values and unintended calls.
static/src/js/util/accessMethods/computeKeywordMappings.js (1)

10-26: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard against undefined variable IDs in keyword mapping computation.

The determineVariableId helper returns string | undefined, but line 24 pushes variableId directly into calculatedMappings[leafNode] without checking for undefined. If a variable has neither a valid conceptId nor href (or has empty strings for both), the resulting keyword mapping children would contain { id: undefined }, violating the KeywordMapping type contract that expects id: string.

This creates an inconsistency with getVariables.ts (line 38 in this PR), which explicitly guards against undefined IDs: if (!variableId) return. For consistency and type safety, skip variables without valid IDs here as well.

🛡️ Proposed fix to skip variables without valid IDs
   items.forEach((variable) => {
     const variableId = determineVariableId(variable)
+
+    // Skip variables that don't have a valid ID
+    if (!variableId) return

     const { scienceKeywords = [] } = variable
🤖 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 `@static/src/js/util/accessMethods/computeKeywordMappings.js` around lines 10 -
26, The loop uses determineVariableId(variable) which can return undefined, but
variableId is pushed into calculatedMappings[leafNode] unguarded; update the
logic in the items.forEach block to skip variables without a valid ID (e.g., if
(!variableId) return/continue) before referencing variableId or pushing into
calculatedMappings[leafNode], ensuring calculatedMappings only receives string
IDs and not undefined.
🧹 Nitpick comments (5)
static/src/js/components/AccessMethod/AccessMethod.jsx (1)

427-434: 💤 Low value

Update or remove outdated comment.

The comment says "We filter the formats based on whether their mimeType exists in the ousFormatMapping" but the code no longer performs any such filtering—it simply maps all supportedOutputFormats to <option> elements.

📝 Suggested clarification
  if (isOpendap) {
-    // We filter the formats based on whether their mimeType exists in the ousFormatMapping,
-    // then map the result to <option> elements.
+    // Map each supported format to an <option> element.
    supportedOutputFormatOptions = supportedOutputFormats
      .map((format) => (
🤖 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 `@static/src/js/components/AccessMethod/AccessMethod.jsx` around lines 427 -
434, The comment above the supportedOutputFormatOptions generation is outdated:
it claims filtering by ousFormatMapping but the code simply maps
supportedOutputFormats to <option> elements; update or remove the comment to
accurately describe current behavior (e.g., "Map supportedOutputFormats to
option elements for each format" or remove the sentence) and keep references to
supportedOutputFormatOptions, supportedOutputFormats and ousFormatMapping in
AccessMethod.jsx only if relevant.
static/src/js/util/request/__tests__/harmonyCapabilitiesRequest.test.js (1)

3-13: ⚡ Quick win

Add a unit test for search() GET/query behavior.

This file only validates constructor defaults. Please add a test asserting search({ collectionId, version }) calls GET with query params (not POST body), since that’s the core contract change.

🤖 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 `@static/src/js/util/request/__tests__/harmonyCapabilitiesRequest.test.js`
around lines 3 - 13, Add a unit test for HarmonyCapabilitiesRequest#search that
verifies it performs an HTTP GET with query parameters (not a POST body):
instantiate HarmonyCapabilitiesRequest (e.g., new
HarmonyCapabilitiesRequest(token, 'prod')), stub or spy the underlying HTTP call
used by search (fetch/axios/getClient) and call request.search({ collectionId,
version }), then assert the spy was invoked with method 'GET' and the request
URL contains the query string ?collectionId=<...>&version=<...> (and no request
body was sent). Ensure the test references HarmonyCapabilitiesRequest and its
search method so it explicitly checks GET/query behavior.
static/src/js/util/request/harmonyCapabilitiesDocumentRequest.ts (1)

4-4: ⚡ Quick win

Use a type-only import for HarmonyRequestParams (line 4).

HarmonyRequestParams is only used in the type position of search(params: HarmonyRequestParams)—switching to import type { HarmonyRequestParams } ... avoids implying a runtime import.

🤖 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 `@static/src/js/util/request/harmonyCapabilitiesDocumentRequest.ts` at line 4,
Change the import of HarmonyRequestParams to a type-only import to avoid
implying a runtime dependency: replace the current import statement that brings
in HarmonyRequestParams with an "import type { HarmonyRequestParams } ..." and
ensure the only usage is the type annotation in the search(params:
HarmonyRequestParams) function so no runtime import is retained.
static/src/js/types/sharedTypes.ts (1)

10-10: ⚡ Quick win

Use a type-only import for HarmonyScienceKeyword.

static/src/js/types/sharedTypes.ts imports HarmonyScienceKeyword as a value (line 10) but it’s only used in type positions (scienceKeywords: HarmonyScienceKeyword[], line 578). Switch to import type { HarmonyScienceKeyword } ... to make intent explicit and align with existing import type usage in this file.

🤖 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 `@static/src/js/types/sharedTypes.ts` at line 10, Change the value import of
HarmonyScienceKeyword to a type-only import: replace the current import of
HarmonyScienceKeyword with an "import type { HarmonyScienceKeyword } ..." so the
symbol is only brought in for type-checking (it's used in type positions like
scienceKeywords: HarmonyScienceKeyword[]). This makes intent explicit and aligns
with existing import type usage; update the import line that currently reads
"import { HarmonyScienceKeyword } ..." to use "import type".
static/src/js/util/accessMethods/__tests__/getVariables.test.js (1)

11-53: 💤 Low value

Consider consolidating duplicate describe blocks for clarity.

Two top-level describe('getVariables', ...) blocks exist at lines 11 and 34 with identical names. While this works, it's confusing for maintainability. Consider either:

  • Combining into a single describe block with nested describes for 'OPeNDAP variables' and 'Harmony variables', or
  • Renaming to describe('getVariables (OPeNDAP)', ...) and describe('getVariables (Harmony)', ...)
♻️ Suggested consolidation
 describe('getVariables', () => {
-  describe('when variables exist', () => {
-    test('correctly formats variables from graphql', () => {
+  describe('OPeNDAP variables', () => {
+    test('correctly formats variables from graphql', () => {
       const { keywordMappings, variables } = getVariables(opendapVariablesResponse)

       expect(keywordMappings).toEqual(mockOpendapKeywordMappings)
       expect(variables).toEqual(mockOpendapVariables)
     })
-  })
-
-  describe('when no variables exist', () => {
-    test('correctly formats variables from graphql', () => {
+
+    test('correctly formats when no variables exist', () => {
       const { keywordMappings, variables } = getVariables({
         count: 0,
         items: null
       })
 
       expect(keywordMappings).toEqual([])
       expect(variables).toEqual({})
     })
   })
-})
-
-describe('getVariables', () => {
-  describe('when variables exist', () => {
-    test('correctly formats variables from the capabilities document', () => {
+
+  describe('Harmony variables', () => {
+    test('correctly formats variables from the capabilities document', () => {
       // Harmony variables come as an array, not an object with `items`
       const { keywordMappings, variables } = getVariables(harmonyVariablesResponse.items)
 
       expect(keywordMappings).toEqual(mockHarmonyKeywordMappings)
       expect(variables).toEqual(mockHarmonyVariables)
     })
-  })
-
-  describe('when no variables exist', () => {
-    test('correctly returns empty data structures', () => {
+
+    test('correctly returns empty data structures when no variables exist', () => {
       const { keywordMappings, variables } = getVariables([])
 
       expect(keywordMappings).toEqual([])
       expect(variables).toEqual({})
     })
   })
 })
🤖 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 `@static/src/js/util/accessMethods/__tests__/getVariables.test.js` around lines
11 - 53, The tests include two separate top-level describe blocks both named
"getVariables" which is confusing; consolidate by either merging them into one
describe('getVariables', ...) containing nested describe blocks (e.g.,
describe('OPeNDAP variables', ...) and describe('Harmony variables', ...) that
wrap the existing tests for opendapVariablesResponse and
harmonyVariablesResponse respectively) or rename the two top-level describes to
unique names (e.g., describe('getVariables (OPeNDAP)') and
describe('getVariables (Harmony)')) so each group is clearly identified; locate
the blocks that reference getVariables, opendapVariablesResponse,
harmonyVariablesResponse, mockOpendapVariables, mockHarmonyVariables,
mockOpendapKeywordMappings and mockHarmonyKeywordMappings and apply the chosen
consolidation/renaming consistently.
🤖 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 `@static/src/js/components/AccessMethod/__tests__/__mocks__/mocks.ts`:
- Line 26: In the mocks file update the misspelled projection code by changing
the mocked object property crs values from 'EPGS:4313' to the correct
'EPSG:4313' so projection comparisons succeed; locate the crs properties in the
mock objects inside
static/src/js/components/AccessMethod/__tests__/__mocks__/mocks.ts and replace
every occurrence (currently 'EPGS:4313') with 'EPSG:4313'.

In `@static/src/js/components/AccessMethod/AccessMethod.jsx`:
- Line 278: Remove the debug console.log in the AccessMethod component by
deleting the statement that logs "🚀 ~ AccessMethod.jsx:330 ~ AccessMethod ~
accessMethods:" (inside the AccessMethod function/component where accessMethods
is referenced) and ensure no other stray console.debug/console.log calls remain
in that file; keep behavior unchanged otherwise and run linting/tests to confirm
no leftover debug prints.

In `@static/src/js/util/__tests__/determineVariableId.test.ts`:
- Around line 5-43: Add a test to determineVariableId that covers the edge case
where a variable object contains both a UmmSVariable.conceptId and a
HarmonyVariable.href; create a combined fixture (or two objects) with conceptId
set to e.g. "V789-CMR" and href containing a different ID, call
determineVariableId on the object, and assert that the returned value equals the
conceptId (verifying precedence of conceptId over href); reference the
determineVariableId function and the UmmSVariable/HarmonyVariable shapes when
adding the test to static/src/js/util/__tests__/determineVariableId.test.ts.

In `@static/src/js/util/determineVariableId.ts`:
- Around line 18-25: The logic in determineVariableId sets variableId from
variable.conceptId and then always re-checks href, causing href to overwrite
conceptId when both exist; change the second check to an else if so conceptId
takes precedence (i.e., keep the existing if ('conceptId' in variable &&
variable.conceptId) { variableId = variable.conceptId } and make the href branch
else if ('href' in variable && variable.href) { variableId =
variable.href.split('/').pop() }) to enforce the documented fallback behavior.

In `@static/src/js/util/getDerivedHarmonyState/getDerivedHarmonyState.ts`:
- Around line 241-246: The spatial subsetting supported flag is only using
summaryBbox, so change the logic that sets spatialSubset.supported to consider
either bbox OR shape being supported (e.g., use summaryBbox || summaryShape)
when computing summary.subsetting.spatial; update the same pattern where it
appears (also at the other occurrence around line 374). Ensure you reference the
summaryBbox and summaryShape variables and set
summary.subsetting.spatial.supported accordingly while leaving temporal/variable
logic (summaryTemporal/summaryVariable) unchanged.

In `@static/src/js/zustand/slices/createProjectSlice.ts`:
- Around line 280-316: The current map callback returns buildPromise(null) on a
Harmony 401, which only exits the individual callback and doesn't short-circuit
Promise.all; change that so the outer Promise.all rejects immediately by
re-throwing the 401 error (or returning Promise.reject(error)) instead of
returning buildPromise(null). Locate the filteredIds.map async callback in
createProjectSlice.ts (the block that constructs
HarmonyCapabilitiesDocumentRequest and calls .search), and replace the `return
buildPromise(null)` inside the `if (error instanceof AxiosError &&
error.response?.status === 401)` branch with `throw error` (or `return
Promise.reject(error)`) so Promise.all rejects and the outer flow stops; keep
the existing zustandState.errors.handleError call intact.
- Around line 779-787: The code destructures selectedAccessMethod and
accessMethods from collection without first ensuring collection exists, so if
the collection was removed the destructure will throw; update the setter to
first check that const collection = byId[collectionId] is truthy (or bail out if
undefined) before doing const { selectedAccessMethod, accessMethods } =
collection — e.g., guard collection (and keep the existing early return for
missing selectedAccessMethod/accessMethods) so the updateAccessMethod branch /
set((state) => { ... }) no-ops safely when the collection is absent.
- Around line 795-850: The current update manually copies selection flags into
harmonyMethod after computing getDerivedHarmonyState, which leaves variables,
hierarchyMappings, and keywordMappings stale; instead, rebuild the entire
Harmony method object using the new derivedHarmonyState (i.e. call the existing
buildHarmony function or the routine that constructs a fresh harmonyMethod)
using updatedSelections and the new derivedHarmonyState, then replace
harmonyMethod with that rebuilt object so variables, hierarchyMappings,
keywordMappings and all availability flags come from the canonical build rather
than piecemeal assignment; ensure you preserve any necessary identity/refs
expected elsewhere (or update callers) and remove the manual field-by-field
syncing that sets enableTemporalSubsetting, enableSpatialSubsetting,
selectedVariables, outputFormatAvailability, etc.

In `@tests/e2e/history/history.spec.js`:
- Around line 89-93: The current page.route matcher uses an exact query-string
and is brittle; update the mock in the tests to match the /capabilities path
more robustly by using a looser route pattern (e.g., '**/capabilities**') and
then inspect the actual request URL inside the handler via route.request().url()
or new URL(...). Check searchParams for collectionId === conceptId and version
=== '2', call route.fulfill({ json: { services: [] } }) when it matches,
otherwise call route.continue() so other queries are not incorrectly mocked.

In `@tests/e2e/page_titles/page_titles.spec.js`:
- Around line 166-170: The route currently matches a full glob including query
params which is fragile; change the page.route registration for the capabilities
endpoint to match the base path (e.g., '**/capabilities') and inside the route
handler inspect route.request().url (or construct a URL object) to check
searchParams for collectionId and version, then call route.fulfill({ json: {
services: [] } }) only when those params match; update the handler around the
existing page.route callback and keep using route.fulfill to respond.

---

Outside diff comments:
In `@static/src/js/components/AccessMethod/AccessMethod.jsx`:
- Around line 118-140: The current useEffect in AccessMethod.jsx that rebuilds
granuleList also references isTemporalSubsettingSelected, isRecurring,
onUpdateAccessMethod, metadata.conceptId, and selectedAccessMethod but only
lists [projectCollection], causing stale closures; split into two effects: keep
the granule-list building effect (useEffect that maps
addedGranuleIds/granulesAllIds using granulesMetadata and calls setGranuleList)
with dependencies [projectCollection, addedGranuleIds, granulesAllIds,
granulesMetadata, setGranuleList], and create a separate effect for the
auto-disable logic that watches [isTemporalSubsettingSelected, isRecurring,
onUpdateAccessMethod, metadata.conceptId, selectedAccessMethod] and calls
onUpdateAccessMethod({ collectionId: metadata.conceptId, method: {
[selectedAccessMethod]: { enableTemporalSubsetting: false } } }) only when
isTemporalSubsettingSelected && isRecurring to avoid stale values and unintended
calls.

In `@static/src/js/util/accessMethods/computeKeywordMappings.js`:
- Around line 10-26: The loop uses determineVariableId(variable) which can
return undefined, but variableId is pushed into calculatedMappings[leafNode]
unguarded; update the logic in the items.forEach block to skip variables without
a valid ID (e.g., if (!variableId) return/continue) before referencing
variableId or pushing into calculatedMappings[leafNode], ensuring
calculatedMappings only receives string IDs and not undefined.

---

Nitpick comments:
In `@static/src/js/components/AccessMethod/AccessMethod.jsx`:
- Around line 427-434: The comment above the supportedOutputFormatOptions
generation is outdated: it claims filtering by ousFormatMapping but the code
simply maps supportedOutputFormats to <option> elements; update or remove the
comment to accurately describe current behavior (e.g., "Map
supportedOutputFormats to option elements for each format" or remove the
sentence) and keep references to supportedOutputFormatOptions,
supportedOutputFormats and ousFormatMapping in AccessMethod.jsx only if
relevant.

In `@static/src/js/types/sharedTypes.ts`:
- Line 10: Change the value import of HarmonyScienceKeyword to a type-only
import: replace the current import of HarmonyScienceKeyword with an "import type
{ HarmonyScienceKeyword } ..." so the symbol is only brought in for
type-checking (it's used in type positions like scienceKeywords:
HarmonyScienceKeyword[]). This makes intent explicit and aligns with existing
import type usage; update the import line that currently reads "import {
HarmonyScienceKeyword } ..." to use "import type".

In `@static/src/js/util/accessMethods/__tests__/getVariables.test.js`:
- Around line 11-53: The tests include two separate top-level describe blocks
both named "getVariables" which is confusing; consolidate by either merging them
into one describe('getVariables', ...) containing nested describe blocks (e.g.,
describe('OPeNDAP variables', ...) and describe('Harmony variables', ...) that
wrap the existing tests for opendapVariablesResponse and
harmonyVariablesResponse respectively) or rename the two top-level describes to
unique names (e.g., describe('getVariables (OPeNDAP)') and
describe('getVariables (Harmony)')) so each group is clearly identified; locate
the blocks that reference getVariables, opendapVariablesResponse,
harmonyVariablesResponse, mockOpendapVariables, mockHarmonyVariables,
mockOpendapKeywordMappings and mockHarmonyKeywordMappings and apply the chosen
consolidation/renaming consistently.

In `@static/src/js/util/request/__tests__/harmonyCapabilitiesRequest.test.js`:
- Around line 3-13: Add a unit test for HarmonyCapabilitiesRequest#search that
verifies it performs an HTTP GET with query parameters (not a POST body):
instantiate HarmonyCapabilitiesRequest (e.g., new
HarmonyCapabilitiesRequest(token, 'prod')), stub or spy the underlying HTTP call
used by search (fetch/axios/getClient) and call request.search({ collectionId,
version }), then assert the spy was invoked with method 'GET' and the request
URL contains the query string ?collectionId=<...>&version=<...> (and no request
body was sent). Ensure the test references HarmonyCapabilitiesRequest and its
search method so it explicitly checks GET/query behavior.

In `@static/src/js/util/request/harmonyCapabilitiesDocumentRequest.ts`:
- Line 4: Change the import of HarmonyRequestParams to a type-only import to
avoid implying a runtime dependency: replace the current import statement that
brings in HarmonyRequestParams with an "import type { HarmonyRequestParams }
..." and ensure the only usage is the type annotation in the search(params:
HarmonyRequestParams) function so no runtime import is retained.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: f2bec23f-1c65-4823-9009-5fa4cfd148ce

📥 Commits

Reviewing files that changed from the base of the PR and between 9b90045 and 9f2ecf7.

📒 Files selected for processing (57)
  • serverless/src/graphQl/resolvers/__tests__/retrieval.test.js
  • sharedUtils/outputFormatMaps.js
  • static.config.json
  • static/src/js/components/AccessMethod/AccessMethod.jsx
  • static/src/js/components/AccessMethod/__tests__/AccessMethod.test.jsx
  • static/src/js/components/AccessMethod/__tests__/EchoForm.test.tsx
  • static/src/js/components/AccessMethod/__tests__/__mocks__/mocks.ts
  • static/src/js/components/AccessMethod/__tests__/mocks.ts
  • static/src/js/components/OrderStatus/__tests__/OrderStatusCollection.test.tsx
  • static/src/js/components/ProjectPanels/ProjectPanels.jsx
  • static/src/js/components/ProjectPanels/__tests__/ProjectPanels.test.jsx
  • static/src/js/hooks/__tests__/useCreateRetrieval.test.tsx
  • static/src/js/hooks/useCreateRetrieval.ts
  • static/src/js/types/sharedTypes.ts
  • static/src/js/util/__tests__/determineVariableId.test.ts
  • static/src/js/util/__tests__/retrievals.test.js
  • static/src/js/util/accessMethods/__tests__/buildAccessMethods.test.js
  • static/src/js/util/accessMethods/__tests__/buildAccessMethods.test.ts
  • static/src/js/util/accessMethods/__tests__/computeKeywordMappings.test.js
  • static/src/js/util/accessMethods/__tests__/defaultConcatenation.test.js
  • static/src/js/util/accessMethods/__tests__/getVariables.test.js
  • static/src/js/util/accessMethods/__tests__/insertSavedAccessConfig.test.js
  • static/src/js/util/accessMethods/__tests__/mocks.js
  • static/src/js/util/accessMethods/__tests__/supportsBoundingBoxSubsetting.test.js
  • static/src/js/util/accessMethods/__tests__/supportsConcatenation.test.js
  • static/src/js/util/accessMethods/__tests__/supportsShapefileSubsetting.test.js
  • static/src/js/util/accessMethods/__tests__/supportsTemporalSubsetting.test.js
  • static/src/js/util/accessMethods/buildAccessMethods.js
  • static/src/js/util/accessMethods/buildAccessMethods.ts
  • static/src/js/util/accessMethods/buildAccessMethods/__tests__/buildHarmony.test.js
  • static/src/js/util/accessMethods/buildAccessMethods/__tests__/buildHarmony.test.ts
  • static/src/js/util/accessMethods/buildAccessMethods/__tests__/buildOpendap.test.js
  • static/src/js/util/accessMethods/buildAccessMethods/buildHarmony.js
  • static/src/js/util/accessMethods/buildAccessMethods/buildHarmony.ts
  • static/src/js/util/accessMethods/buildAccessMethods/buildOpendap.js
  • static/src/js/util/accessMethods/computeHierarchyMappings.js
  • static/src/js/util/accessMethods/computeKeywordMappings.js
  • static/src/js/util/accessMethods/defaultConcatenation.js
  • static/src/js/util/accessMethods/getVariables.js
  • static/src/js/util/accessMethods/getVariables.ts
  • static/src/js/util/accessMethods/supportsBoundingBoxSubsetting.js
  • static/src/js/util/accessMethods/supportsConcatenation.js
  • static/src/js/util/accessMethods/supportsShapefileSubsetting.js
  • static/src/js/util/accessMethods/supportsTemporalSubsetting.js
  • static/src/js/util/determineVariableId.ts
  • static/src/js/util/getDerivedHarmonyState/__tests__/__mocks__/mocks.ts
  • static/src/js/util/getDerivedHarmonyState/__tests__/getDerivedHarmonyState.test.ts
  • static/src/js/util/getDerivedHarmonyState/getDerivedHarmonyState.ts
  • static/src/js/util/request/__tests__/harmonyCapabilitiesRequest.test.js
  • static/src/js/util/request/harmonyCapabilitiesDocumentRequest.ts
  • static/src/js/util/request/request.ts
  • static/src/js/util/retrievals.js
  • static/src/js/zustand/slices/__tests__/createProjectSlice.test.ts
  • static/src/js/zustand/slices/createProjectSlice.ts
  • static/src/js/zustand/types.ts
  • tests/e2e/history/history.spec.js
  • tests/e2e/page_titles/page_titles.spec.js
💤 Files with no reviewable changes (19)
  • static/src/js/util/accessMethods/tests/defaultConcatenation.test.js
  • static/src/js/util/accessMethods/buildAccessMethods/tests/buildHarmony.test.js
  • static/src/js/util/accessMethods/defaultConcatenation.js
  • static/src/js/util/accessMethods/supportsBoundingBoxSubsetting.js
  • static/src/js/util/accessMethods/supportsShapefileSubsetting.js
  • static/src/js/util/accessMethods/supportsTemporalSubsetting.js
  • static/src/js/util/accessMethods/tests/supportsBoundingBoxSubsetting.test.js
  • static/src/js/components/OrderStatus/tests/OrderStatusCollection.test.tsx
  • static/src/js/util/accessMethods/buildAccessMethods/buildHarmony.js
  • static/src/js/components/AccessMethod/tests/mocks.ts
  • static/src/js/util/accessMethods/tests/supportsTemporalSubsetting.test.js
  • static/src/js/util/accessMethods/supportsConcatenation.js
  • static/src/js/util/accessMethods/buildAccessMethods.js
  • static/src/js/util/accessMethods/getVariables.js
  • static/src/js/util/accessMethods/tests/supportsShapefileSubsetting.test.js
  • serverless/src/graphQl/resolvers/tests/retrieval.test.js
  • static/src/js/util/retrievals.js
  • sharedUtils/outputFormatMaps.js
  • static/src/js/util/accessMethods/tests/supportsConcatenation.test.js

@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

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 10

Caution

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

⚠️ Outside diff range comments (2)
static/src/js/components/AccessMethod/AccessMethod.jsx (1)

118-140: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix useEffect dependency array to prevent stale closure bugs.

The effect uses isTemporalSubsettingSelected, isRecurring, onUpdateAccessMethod, metadata.conceptId, and selectedAccessMethod, but only lists [projectCollection] in the dependency array. This can cause the effect to use stale values if those variables change without projectCollection changing.

Additionally, the auto-disable logic for recurring temporal subsetting should probably be in a separate useEffect that specifically watches isRecurring and isTemporalSubsettingSelected, rather than being coupled to granule list rebuilding.

🔧 Recommended fix

Split into two separate effects:

  useEffect(() => {
    if (addedGranuleIds.length > 0) {
      granulesToDisplay = addedGranuleIds
    } else {
      granulesToDisplay = granulesAllIds
    }

    // Build the list of granules
    const granuleListObj = granulesToDisplay.map((id) => granulesMetadata[id])
    setGranuleList(granuleListObj)
+  }, [projectCollection])
+
+  // Disable temporal subsetting if the user has a recurring date selected
+  useEffect(() => {
-    // Disable temporal subsetting if the user has a recurring date selected
    if (isTemporalSubsettingSelected && isRecurring) {
      onUpdateAccessMethod({
        collectionId: metadata.conceptId,
        method: {
          [selectedAccessMethod]: {
            enableTemporalSubsetting: false
          }
        }
      })
    }
-  }, [projectCollection])
+  }, [isTemporalSubsettingSelected, isRecurring, onUpdateAccessMethod, metadata.conceptId, selectedAccessMethod])
🤖 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 `@static/src/js/components/AccessMethod/AccessMethod.jsx` around lines 118 -
140, The current useEffect in AccessMethod.jsx that rebuilds granuleList also
references isTemporalSubsettingSelected, isRecurring, onUpdateAccessMethod,
metadata.conceptId, and selectedAccessMethod but only lists [projectCollection],
causing stale closures; split into two effects: keep the granule-list building
effect (useEffect that maps addedGranuleIds/granulesAllIds using
granulesMetadata and calls setGranuleList) with dependencies [projectCollection,
addedGranuleIds, granulesAllIds, granulesMetadata, setGranuleList], and create a
separate effect for the auto-disable logic that watches
[isTemporalSubsettingSelected, isRecurring, onUpdateAccessMethod,
metadata.conceptId, selectedAccessMethod] and calls onUpdateAccessMethod({
collectionId: metadata.conceptId, method: { [selectedAccessMethod]: {
enableTemporalSubsetting: false } } }) only when isTemporalSubsettingSelected &&
isRecurring to avoid stale values and unintended calls.
static/src/js/util/accessMethods/computeKeywordMappings.js (1)

10-26: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard against undefined variable IDs in keyword mapping computation.

The determineVariableId helper returns string | undefined, but line 24 pushes variableId directly into calculatedMappings[leafNode] without checking for undefined. If a variable has neither a valid conceptId nor href (or has empty strings for both), the resulting keyword mapping children would contain { id: undefined }, violating the KeywordMapping type contract that expects id: string.

This creates an inconsistency with getVariables.ts (line 38 in this PR), which explicitly guards against undefined IDs: if (!variableId) return. For consistency and type safety, skip variables without valid IDs here as well.

🛡️ Proposed fix to skip variables without valid IDs
   items.forEach((variable) => {
     const variableId = determineVariableId(variable)
+
+    // Skip variables that don't have a valid ID
+    if (!variableId) return

     const { scienceKeywords = [] } = variable
🤖 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 `@static/src/js/util/accessMethods/computeKeywordMappings.js` around lines 10 -
26, The loop uses determineVariableId(variable) which can return undefined, but
variableId is pushed into calculatedMappings[leafNode] unguarded; update the
logic in the items.forEach block to skip variables without a valid ID (e.g., if
(!variableId) return/continue) before referencing variableId or pushing into
calculatedMappings[leafNode], ensuring calculatedMappings only receives string
IDs and not undefined.
🧹 Nitpick comments (5)
static/src/js/components/AccessMethod/AccessMethod.jsx (1)

427-434: 💤 Low value

Update or remove outdated comment.

The comment says "We filter the formats based on whether their mimeType exists in the ousFormatMapping" but the code no longer performs any such filtering—it simply maps all supportedOutputFormats to <option> elements.

📝 Suggested clarification
  if (isOpendap) {
-    // We filter the formats based on whether their mimeType exists in the ousFormatMapping,
-    // then map the result to <option> elements.
+    // Map each supported format to an <option> element.
    supportedOutputFormatOptions = supportedOutputFormats
      .map((format) => (
🤖 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 `@static/src/js/components/AccessMethod/AccessMethod.jsx` around lines 427 -
434, The comment above the supportedOutputFormatOptions generation is outdated:
it claims filtering by ousFormatMapping but the code simply maps
supportedOutputFormats to <option> elements; update or remove the comment to
accurately describe current behavior (e.g., "Map supportedOutputFormats to
option elements for each format" or remove the sentence) and keep references to
supportedOutputFormatOptions, supportedOutputFormats and ousFormatMapping in
AccessMethod.jsx only if relevant.
static/src/js/util/request/__tests__/harmonyCapabilitiesRequest.test.js (1)

3-13: ⚡ Quick win

Add a unit test for search() GET/query behavior.

This file only validates constructor defaults. Please add a test asserting search({ collectionId, version }) calls GET with query params (not POST body), since that’s the core contract change.

🤖 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 `@static/src/js/util/request/__tests__/harmonyCapabilitiesRequest.test.js`
around lines 3 - 13, Add a unit test for HarmonyCapabilitiesRequest#search that
verifies it performs an HTTP GET with query parameters (not a POST body):
instantiate HarmonyCapabilitiesRequest (e.g., new
HarmonyCapabilitiesRequest(token, 'prod')), stub or spy the underlying HTTP call
used by search (fetch/axios/getClient) and call request.search({ collectionId,
version }), then assert the spy was invoked with method 'GET' and the request
URL contains the query string ?collectionId=<...>&version=<...> (and no request
body was sent). Ensure the test references HarmonyCapabilitiesRequest and its
search method so it explicitly checks GET/query behavior.
static/src/js/util/request/harmonyCapabilitiesDocumentRequest.ts (1)

4-4: ⚡ Quick win

Use a type-only import for HarmonyRequestParams (line 4).

HarmonyRequestParams is only used in the type position of search(params: HarmonyRequestParams)—switching to import type { HarmonyRequestParams } ... avoids implying a runtime import.

🤖 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 `@static/src/js/util/request/harmonyCapabilitiesDocumentRequest.ts` at line 4,
Change the import of HarmonyRequestParams to a type-only import to avoid
implying a runtime dependency: replace the current import statement that brings
in HarmonyRequestParams with an "import type { HarmonyRequestParams } ..." and
ensure the only usage is the type annotation in the search(params:
HarmonyRequestParams) function so no runtime import is retained.
static/src/js/types/sharedTypes.ts (1)

10-10: ⚡ Quick win

Use a type-only import for HarmonyScienceKeyword.

static/src/js/types/sharedTypes.ts imports HarmonyScienceKeyword as a value (line 10) but it’s only used in type positions (scienceKeywords: HarmonyScienceKeyword[], line 578). Switch to import type { HarmonyScienceKeyword } ... to make intent explicit and align with existing import type usage in this file.

🤖 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 `@static/src/js/types/sharedTypes.ts` at line 10, Change the value import of
HarmonyScienceKeyword to a type-only import: replace the current import of
HarmonyScienceKeyword with an "import type { HarmonyScienceKeyword } ..." so the
symbol is only brought in for type-checking (it's used in type positions like
scienceKeywords: HarmonyScienceKeyword[]). This makes intent explicit and aligns
with existing import type usage; update the import line that currently reads
"import { HarmonyScienceKeyword } ..." to use "import type".
static/src/js/util/accessMethods/__tests__/getVariables.test.js (1)

11-53: 💤 Low value

Consider consolidating duplicate describe blocks for clarity.

Two top-level describe('getVariables', ...) blocks exist at lines 11 and 34 with identical names. While this works, it's confusing for maintainability. Consider either:

  • Combining into a single describe block with nested describes for 'OPeNDAP variables' and 'Harmony variables', or
  • Renaming to describe('getVariables (OPeNDAP)', ...) and describe('getVariables (Harmony)', ...)
♻️ Suggested consolidation
 describe('getVariables', () => {
-  describe('when variables exist', () => {
-    test('correctly formats variables from graphql', () => {
+  describe('OPeNDAP variables', () => {
+    test('correctly formats variables from graphql', () => {
       const { keywordMappings, variables } = getVariables(opendapVariablesResponse)

       expect(keywordMappings).toEqual(mockOpendapKeywordMappings)
       expect(variables).toEqual(mockOpendapVariables)
     })
-  })
-
-  describe('when no variables exist', () => {
-    test('correctly formats variables from graphql', () => {
+
+    test('correctly formats when no variables exist', () => {
       const { keywordMappings, variables } = getVariables({
         count: 0,
         items: null
       })
 
       expect(keywordMappings).toEqual([])
       expect(variables).toEqual({})
     })
   })
-})
-
-describe('getVariables', () => {
-  describe('when variables exist', () => {
-    test('correctly formats variables from the capabilities document', () => {
+
+  describe('Harmony variables', () => {
+    test('correctly formats variables from the capabilities document', () => {
       // Harmony variables come as an array, not an object with `items`
       const { keywordMappings, variables } = getVariables(harmonyVariablesResponse.items)
 
       expect(keywordMappings).toEqual(mockHarmonyKeywordMappings)
       expect(variables).toEqual(mockHarmonyVariables)
     })
-  })
-
-  describe('when no variables exist', () => {
-    test('correctly returns empty data structures', () => {
+
+    test('correctly returns empty data structures when no variables exist', () => {
       const { keywordMappings, variables } = getVariables([])
 
       expect(keywordMappings).toEqual([])
       expect(variables).toEqual({})
     })
   })
 })
🤖 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 `@static/src/js/util/accessMethods/__tests__/getVariables.test.js` around lines
11 - 53, The tests include two separate top-level describe blocks both named
"getVariables" which is confusing; consolidate by either merging them into one
describe('getVariables', ...) containing nested describe blocks (e.g.,
describe('OPeNDAP variables', ...) and describe('Harmony variables', ...) that
wrap the existing tests for opendapVariablesResponse and
harmonyVariablesResponse respectively) or rename the two top-level describes to
unique names (e.g., describe('getVariables (OPeNDAP)') and
describe('getVariables (Harmony)')) so each group is clearly identified; locate
the blocks that reference getVariables, opendapVariablesResponse,
harmonyVariablesResponse, mockOpendapVariables, mockHarmonyVariables,
mockOpendapKeywordMappings and mockHarmonyKeywordMappings and apply the chosen
consolidation/renaming consistently.
🤖 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 `@static/src/js/components/AccessMethod/__tests__/__mocks__/mocks.ts`:
- Line 26: In the mocks file update the misspelled projection code by changing
the mocked object property crs values from 'EPGS:4313' to the correct
'EPSG:4313' so projection comparisons succeed; locate the crs properties in the
mock objects inside
static/src/js/components/AccessMethod/__tests__/__mocks__/mocks.ts and replace
every occurrence (currently 'EPGS:4313') with 'EPSG:4313'.

In `@static/src/js/components/AccessMethod/AccessMethod.jsx`:
- Line 278: Remove the debug console.log in the AccessMethod component by
deleting the statement that logs "🚀 ~ AccessMethod.jsx:330 ~ AccessMethod ~
accessMethods:" (inside the AccessMethod function/component where accessMethods
is referenced) and ensure no other stray console.debug/console.log calls remain
in that file; keep behavior unchanged otherwise and run linting/tests to confirm
no leftover debug prints.

In `@static/src/js/util/__tests__/determineVariableId.test.ts`:
- Around line 5-43: Add a test to determineVariableId that covers the edge case
where a variable object contains both a UmmSVariable.conceptId and a
HarmonyVariable.href; create a combined fixture (or two objects) with conceptId
set to e.g. "V789-CMR" and href containing a different ID, call
determineVariableId on the object, and assert that the returned value equals the
conceptId (verifying precedence of conceptId over href); reference the
determineVariableId function and the UmmSVariable/HarmonyVariable shapes when
adding the test to static/src/js/util/__tests__/determineVariableId.test.ts.

In `@static/src/js/util/determineVariableId.ts`:
- Around line 18-25: The logic in determineVariableId sets variableId from
variable.conceptId and then always re-checks href, causing href to overwrite
conceptId when both exist; change the second check to an else if so conceptId
takes precedence (i.e., keep the existing if ('conceptId' in variable &&
variable.conceptId) { variableId = variable.conceptId } and make the href branch
else if ('href' in variable && variable.href) { variableId =
variable.href.split('/').pop() }) to enforce the documented fallback behavior.

In `@static/src/js/util/getDerivedHarmonyState/getDerivedHarmonyState.ts`:
- Around line 241-246: The spatial subsetting supported flag is only using
summaryBbox, so change the logic that sets spatialSubset.supported to consider
either bbox OR shape being supported (e.g., use summaryBbox || summaryShape)
when computing summary.subsetting.spatial; update the same pattern where it
appears (also at the other occurrence around line 374). Ensure you reference the
summaryBbox and summaryShape variables and set
summary.subsetting.spatial.supported accordingly while leaving temporal/variable
logic (summaryTemporal/summaryVariable) unchanged.

In `@static/src/js/zustand/slices/createProjectSlice.ts`:
- Around line 280-316: The current map callback returns buildPromise(null) on a
Harmony 401, which only exits the individual callback and doesn't short-circuit
Promise.all; change that so the outer Promise.all rejects immediately by
re-throwing the 401 error (or returning Promise.reject(error)) instead of
returning buildPromise(null). Locate the filteredIds.map async callback in
createProjectSlice.ts (the block that constructs
HarmonyCapabilitiesDocumentRequest and calls .search), and replace the `return
buildPromise(null)` inside the `if (error instanceof AxiosError &&
error.response?.status === 401)` branch with `throw error` (or `return
Promise.reject(error)`) so Promise.all rejects and the outer flow stops; keep
the existing zustandState.errors.handleError call intact.
- Around line 779-787: The code destructures selectedAccessMethod and
accessMethods from collection without first ensuring collection exists, so if
the collection was removed the destructure will throw; update the setter to
first check that const collection = byId[collectionId] is truthy (or bail out if
undefined) before doing const { selectedAccessMethod, accessMethods } =
collection — e.g., guard collection (and keep the existing early return for
missing selectedAccessMethod/accessMethods) so the updateAccessMethod branch /
set((state) => { ... }) no-ops safely when the collection is absent.
- Around line 795-850: The current update manually copies selection flags into
harmonyMethod after computing getDerivedHarmonyState, which leaves variables,
hierarchyMappings, and keywordMappings stale; instead, rebuild the entire
Harmony method object using the new derivedHarmonyState (i.e. call the existing
buildHarmony function or the routine that constructs a fresh harmonyMethod)
using updatedSelections and the new derivedHarmonyState, then replace
harmonyMethod with that rebuilt object so variables, hierarchyMappings,
keywordMappings and all availability flags come from the canonical build rather
than piecemeal assignment; ensure you preserve any necessary identity/refs
expected elsewhere (or update callers) and remove the manual field-by-field
syncing that sets enableTemporalSubsetting, enableSpatialSubsetting,
selectedVariables, outputFormatAvailability, etc.

In `@tests/e2e/history/history.spec.js`:
- Around line 89-93: The current page.route matcher uses an exact query-string
and is brittle; update the mock in the tests to match the /capabilities path
more robustly by using a looser route pattern (e.g., '**/capabilities**') and
then inspect the actual request URL inside the handler via route.request().url()
or new URL(...). Check searchParams for collectionId === conceptId and version
=== '2', call route.fulfill({ json: { services: [] } }) when it matches,
otherwise call route.continue() so other queries are not incorrectly mocked.

In `@tests/e2e/page_titles/page_titles.spec.js`:
- Around line 166-170: The route currently matches a full glob including query
params which is fragile; change the page.route registration for the capabilities
endpoint to match the base path (e.g., '**/capabilities') and inside the route
handler inspect route.request().url (or construct a URL object) to check
searchParams for collectionId and version, then call route.fulfill({ json: {
services: [] } }) only when those params match; update the handler around the
existing page.route callback and keep using route.fulfill to respond.

---

Outside diff comments:
In `@static/src/js/components/AccessMethod/AccessMethod.jsx`:
- Around line 118-140: The current useEffect in AccessMethod.jsx that rebuilds
granuleList also references isTemporalSubsettingSelected, isRecurring,
onUpdateAccessMethod, metadata.conceptId, and selectedAccessMethod but only
lists [projectCollection], causing stale closures; split into two effects: keep
the granule-list building effect (useEffect that maps
addedGranuleIds/granulesAllIds using granulesMetadata and calls setGranuleList)
with dependencies [projectCollection, addedGranuleIds, granulesAllIds,
granulesMetadata, setGranuleList], and create a separate effect for the
auto-disable logic that watches [isTemporalSubsettingSelected, isRecurring,
onUpdateAccessMethod, metadata.conceptId, selectedAccessMethod] and calls
onUpdateAccessMethod({ collectionId: metadata.conceptId, method: {
[selectedAccessMethod]: { enableTemporalSubsetting: false } } }) only when
isTemporalSubsettingSelected && isRecurring to avoid stale values and unintended
calls.

In `@static/src/js/util/accessMethods/computeKeywordMappings.js`:
- Around line 10-26: The loop uses determineVariableId(variable) which can
return undefined, but variableId is pushed into calculatedMappings[leafNode]
unguarded; update the logic in the items.forEach block to skip variables without
a valid ID (e.g., if (!variableId) return/continue) before referencing
variableId or pushing into calculatedMappings[leafNode], ensuring
calculatedMappings only receives string IDs and not undefined.

---

Nitpick comments:
In `@static/src/js/components/AccessMethod/AccessMethod.jsx`:
- Around line 427-434: The comment above the supportedOutputFormatOptions
generation is outdated: it claims filtering by ousFormatMapping but the code
simply maps supportedOutputFormats to <option> elements; update or remove the
comment to accurately describe current behavior (e.g., "Map
supportedOutputFormats to option elements for each format" or remove the
sentence) and keep references to supportedOutputFormatOptions,
supportedOutputFormats and ousFormatMapping in AccessMethod.jsx only if
relevant.

In `@static/src/js/types/sharedTypes.ts`:
- Line 10: Change the value import of HarmonyScienceKeyword to a type-only
import: replace the current import of HarmonyScienceKeyword with an "import type
{ HarmonyScienceKeyword } ..." so the symbol is only brought in for
type-checking (it's used in type positions like scienceKeywords:
HarmonyScienceKeyword[]). This makes intent explicit and aligns with existing
import type usage; update the import line that currently reads "import {
HarmonyScienceKeyword } ..." to use "import type".

In `@static/src/js/util/accessMethods/__tests__/getVariables.test.js`:
- Around line 11-53: The tests include two separate top-level describe blocks
both named "getVariables" which is confusing; consolidate by either merging them
into one describe('getVariables', ...) containing nested describe blocks (e.g.,
describe('OPeNDAP variables', ...) and describe('Harmony variables', ...) that
wrap the existing tests for opendapVariablesResponse and
harmonyVariablesResponse respectively) or rename the two top-level describes to
unique names (e.g., describe('getVariables (OPeNDAP)') and
describe('getVariables (Harmony)')) so each group is clearly identified; locate
the blocks that reference getVariables, opendapVariablesResponse,
harmonyVariablesResponse, mockOpendapVariables, mockHarmonyVariables,
mockOpendapKeywordMappings and mockHarmonyKeywordMappings and apply the chosen
consolidation/renaming consistently.

In `@static/src/js/util/request/__tests__/harmonyCapabilitiesRequest.test.js`:
- Around line 3-13: Add a unit test for HarmonyCapabilitiesRequest#search that
verifies it performs an HTTP GET with query parameters (not a POST body):
instantiate HarmonyCapabilitiesRequest (e.g., new
HarmonyCapabilitiesRequest(token, 'prod')), stub or spy the underlying HTTP call
used by search (fetch/axios/getClient) and call request.search({ collectionId,
version }), then assert the spy was invoked with method 'GET' and the request
URL contains the query string ?collectionId=<...>&version=<...> (and no request
body was sent). Ensure the test references HarmonyCapabilitiesRequest and its
search method so it explicitly checks GET/query behavior.

In `@static/src/js/util/request/harmonyCapabilitiesDocumentRequest.ts`:
- Line 4: Change the import of HarmonyRequestParams to a type-only import to
avoid implying a runtime dependency: replace the current import statement that
brings in HarmonyRequestParams with an "import type { HarmonyRequestParams }
..." and ensure the only usage is the type annotation in the search(params:
HarmonyRequestParams) function so no runtime import is retained.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: f2bec23f-1c65-4823-9009-5fa4cfd148ce

📥 Commits

Reviewing files that changed from the base of the PR and between 9b90045 and 9f2ecf7.

📒 Files selected for processing (57)
  • serverless/src/graphQl/resolvers/__tests__/retrieval.test.js
  • sharedUtils/outputFormatMaps.js
  • static.config.json
  • static/src/js/components/AccessMethod/AccessMethod.jsx
  • static/src/js/components/AccessMethod/__tests__/AccessMethod.test.jsx
  • static/src/js/components/AccessMethod/__tests__/EchoForm.test.tsx
  • static/src/js/components/AccessMethod/__tests__/__mocks__/mocks.ts
  • static/src/js/components/AccessMethod/__tests__/mocks.ts
  • static/src/js/components/OrderStatus/__tests__/OrderStatusCollection.test.tsx
  • static/src/js/components/ProjectPanels/ProjectPanels.jsx
  • static/src/js/components/ProjectPanels/__tests__/ProjectPanels.test.jsx
  • static/src/js/hooks/__tests__/useCreateRetrieval.test.tsx
  • static/src/js/hooks/useCreateRetrieval.ts
  • static/src/js/types/sharedTypes.ts
  • static/src/js/util/__tests__/determineVariableId.test.ts
  • static/src/js/util/__tests__/retrievals.test.js
  • static/src/js/util/accessMethods/__tests__/buildAccessMethods.test.js
  • static/src/js/util/accessMethods/__tests__/buildAccessMethods.test.ts
  • static/src/js/util/accessMethods/__tests__/computeKeywordMappings.test.js
  • static/src/js/util/accessMethods/__tests__/defaultConcatenation.test.js
  • static/src/js/util/accessMethods/__tests__/getVariables.test.js
  • static/src/js/util/accessMethods/__tests__/insertSavedAccessConfig.test.js
  • static/src/js/util/accessMethods/__tests__/mocks.js
  • static/src/js/util/accessMethods/__tests__/supportsBoundingBoxSubsetting.test.js
  • static/src/js/util/accessMethods/__tests__/supportsConcatenation.test.js
  • static/src/js/util/accessMethods/__tests__/supportsShapefileSubsetting.test.js
  • static/src/js/util/accessMethods/__tests__/supportsTemporalSubsetting.test.js
  • static/src/js/util/accessMethods/buildAccessMethods.js
  • static/src/js/util/accessMethods/buildAccessMethods.ts
  • static/src/js/util/accessMethods/buildAccessMethods/__tests__/buildHarmony.test.js
  • static/src/js/util/accessMethods/buildAccessMethods/__tests__/buildHarmony.test.ts
  • static/src/js/util/accessMethods/buildAccessMethods/__tests__/buildOpendap.test.js
  • static/src/js/util/accessMethods/buildAccessMethods/buildHarmony.js
  • static/src/js/util/accessMethods/buildAccessMethods/buildHarmony.ts
  • static/src/js/util/accessMethods/buildAccessMethods/buildOpendap.js
  • static/src/js/util/accessMethods/computeHierarchyMappings.js
  • static/src/js/util/accessMethods/computeKeywordMappings.js
  • static/src/js/util/accessMethods/defaultConcatenation.js
  • static/src/js/util/accessMethods/getVariables.js
  • static/src/js/util/accessMethods/getVariables.ts
  • static/src/js/util/accessMethods/supportsBoundingBoxSubsetting.js
  • static/src/js/util/accessMethods/supportsConcatenation.js
  • static/src/js/util/accessMethods/supportsShapefileSubsetting.js
  • static/src/js/util/accessMethods/supportsTemporalSubsetting.js
  • static/src/js/util/determineVariableId.ts
  • static/src/js/util/getDerivedHarmonyState/__tests__/__mocks__/mocks.ts
  • static/src/js/util/getDerivedHarmonyState/__tests__/getDerivedHarmonyState.test.ts
  • static/src/js/util/getDerivedHarmonyState/getDerivedHarmonyState.ts
  • static/src/js/util/request/__tests__/harmonyCapabilitiesRequest.test.js
  • static/src/js/util/request/harmonyCapabilitiesDocumentRequest.ts
  • static/src/js/util/request/request.ts
  • static/src/js/util/retrievals.js
  • static/src/js/zustand/slices/__tests__/createProjectSlice.test.ts
  • static/src/js/zustand/slices/createProjectSlice.ts
  • static/src/js/zustand/types.ts
  • tests/e2e/history/history.spec.js
  • tests/e2e/page_titles/page_titles.spec.js
💤 Files with no reviewable changes (19)
  • static/src/js/util/accessMethods/tests/defaultConcatenation.test.js
  • static/src/js/util/accessMethods/buildAccessMethods/tests/buildHarmony.test.js
  • static/src/js/util/accessMethods/defaultConcatenation.js
  • static/src/js/util/accessMethods/supportsBoundingBoxSubsetting.js
  • static/src/js/util/accessMethods/supportsShapefileSubsetting.js
  • static/src/js/util/accessMethods/supportsTemporalSubsetting.js
  • static/src/js/util/accessMethods/tests/supportsBoundingBoxSubsetting.test.js
  • static/src/js/components/OrderStatus/tests/OrderStatusCollection.test.tsx
  • static/src/js/util/accessMethods/buildAccessMethods/buildHarmony.js
  • static/src/js/components/AccessMethod/tests/mocks.ts
  • static/src/js/util/accessMethods/tests/supportsTemporalSubsetting.test.js
  • static/src/js/util/accessMethods/supportsConcatenation.js
  • static/src/js/util/accessMethods/buildAccessMethods.js
  • static/src/js/util/accessMethods/getVariables.js
  • static/src/js/util/accessMethods/tests/supportsShapefileSubsetting.test.js
  • serverless/src/graphQl/resolvers/tests/retrieval.test.js
  • static/src/js/util/retrievals.js
  • sharedUtils/outputFormatMaps.js
  • static/src/js/util/accessMethods/tests/supportsConcatenation.test.js
🛑 Comments failed to post (10)
static/src/js/components/AccessMethod/__tests__/__mocks__/mocks.ts (1)

26-26: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix typo in EPSG projection code.

The projection CRS code is misspelled as EPGS:4313 in multiple places; it should be EPSG:4313 (note the 'P'). This typo could cause projection matching failures if the code performs strict string comparisons.

🔧 Proposed fix
                  {
                    name: 'Geographic',
-                    crs: 'EPGS:4313'
+                    crs: 'EPSG:4313'
                  }

Apply the same correction at lines 54, 106, and 173.

Also applies to: 54-54, 106-106, 173-173

🤖 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 `@static/src/js/components/AccessMethod/__tests__/__mocks__/mocks.ts` at line
26, In the mocks file update the misspelled projection code by changing the
mocked object property crs values from 'EPGS:4313' to the correct 'EPSG:4313' so
projection comparisons succeed; locate the crs properties in the mock objects
inside static/src/js/components/AccessMethod/__tests__/__mocks__/mocks.ts and
replace every occurrence (currently 'EPGS:4313') with 'EPSG:4313'.
static/src/js/components/AccessMethod/AccessMethod.jsx (1)

278-278: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Remove debug console.log before merging.

This debug logging statement should be removed from production code.

🧹 Proposed fix
-      console.log('🚀 ~ AccessMethod.jsx:330 ~ AccessMethod ~ accessMethods:', accessMethods)
📝 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.


🤖 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 `@static/src/js/components/AccessMethod/AccessMethod.jsx` at line 278, Remove
the debug console.log in the AccessMethod component by deleting the statement
that logs "🚀 ~ AccessMethod.jsx:330 ~ AccessMethod ~ accessMethods:" (inside
the AccessMethod function/component where accessMethods is referenced) and
ensure no other stray console.debug/console.log calls remain in that file; keep
behavior unchanged otherwise and run linting/tests to confirm no leftover debug
prints.
static/src/js/util/__tests__/determineVariableId.test.ts (1)

5-43: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add test case for variables with both conceptId and href.

The test suite is missing coverage for the edge case where a variable has both conceptId and href present. This test would verify the documented precedence behavior (conceptId should win) and would have caught the logic error flagged in the implementation.

🧪 Proposed test case to add
   test('returns undefined when no variable information is blank from harmony', () => {
     const harmonyVariable: HarmonyVariable = {
       name: '',
       href: '',
       scienceKeywords: [],
       longName: '',
       units: ''
     }
     const result = determineVariableId(harmonyVariable)
     expect(result).toEqual(undefined)
   })
+
+  test('prefers conceptId when both conceptId and href are present', () => {
+    // Construct a hybrid object with both fields (edge case)
+    const hybridVariable = {
+      conceptId: 'V123-CMR',
+      name: 'Test Variable',
+      href: 'https://cmr.example.com/search/concepts/V456-HARMONY',
+      scienceKeywords: [],
+      definition: '',
+      instanceInformation: null,
+      longName: '',
+      nativeId: '',
+      units: ''
+    }
+    const result = determineVariableId(hybridVariable as any)
+    expect(result).toEqual('V123-CMR')
+  })
 })
🤖 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 `@static/src/js/util/__tests__/determineVariableId.test.ts` around lines 5 -
43, Add a test to determineVariableId that covers the edge case where a variable
object contains both a UmmSVariable.conceptId and a HarmonyVariable.href; create
a combined fixture (or two objects) with conceptId set to e.g. "V789-CMR" and
href containing a different ID, call determineVariableId on the object, and
assert that the returned value equals the conceptId (verifying precedence of
conceptId over href); reference the determineVariableId function and the
UmmSVariable/HarmonyVariable shapes when adding the test to
static/src/js/util/__tests__/determineVariableId.test.ts.
static/src/js/util/determineVariableId.ts (1)

18-25: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical logic error: href overwrites conceptId instead of falling back.

The implementation uses two independent if statements, so when a variable has both conceptId and href, the function will:

  1. Set variableId = variable.conceptId (line 19)
  2. Then overwrite it with variable.href.split('/').pop() (line 24)

This contradicts the documented behavior on line 8: "Prefers the conceptId, but will fall back to parsing the href for Harmony variables."

🐛 Proposed fix: use `else if` to enforce conceptId precedence
   // Use a type guard to check for 'conceptId'.
   // The `variable.conceptId` check ensures we don't return an empty string.
   if ('conceptId' in variable && variable.conceptId) {
     variableId = variable.conceptId
-  }
-
-  // Use a type guard to check for 'href'. This is the fallback for Harmony variables.
-  if ('href' in variable && variable.href) {
+  } else if ('href' in variable && variable.href) {
+    // Fallback to href for Harmony variables when conceptId is absent
     variableId = variable.href.split('/').pop()
   }
🤖 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 `@static/src/js/util/determineVariableId.ts` around lines 18 - 25, The logic in
determineVariableId sets variableId from variable.conceptId and then always
re-checks href, causing href to overwrite conceptId when both exist; change the
second check to an else if so conceptId takes precedence (i.e., keep the
existing if ('conceptId' in variable && variable.conceptId) { variableId =
variable.conceptId } and make the href branch else if ('href' in variable &&
variable.href) { variableId = variable.href.split('/').pop() }) to enforce the
documented fallback behavior.
static/src/js/util/getDerivedHarmonyState/getDerivedHarmonyState.ts (1)

241-246: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Compute spatial support from bbox OR shape, not bbox alone.

spatialSubset.supported currently mirrors only summaryBbox. If shape is supported while bbox is not, spatial subsetting is incorrectly reported as unsupported.

💡 Suggested fix
     summary: {
       subsetting: {
         bbox: summaryBbox = false,
+        shape: summaryShape = false,
         temporal: summaryTemporal = false,
         variable: summaryVariable = false
       },
@@
       spatialSubset: {
-        supported: summaryBbox,
+        supported: summaryBbox || summaryShape,
         disabled: disabledCapabilities.spatialSubset,
         shapeDisabled: disabledCapabilities.shape,
         bboxDisabled: disabledCapabilities.bbox
       },

Also applies to: 374-374

🤖 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 `@static/src/js/util/getDerivedHarmonyState/getDerivedHarmonyState.ts` around
lines 241 - 246, The spatial subsetting supported flag is only using
summaryBbox, so change the logic that sets spatialSubset.supported to consider
either bbox OR shape being supported (e.g., use summaryBbox || summaryShape)
when computing summary.subsetting.spatial; update the same pattern where it
appears (also at the other occurrence around line 374). Ensure you reference the
summaryBbox and summaryShape variables and set
summary.subsetting.spatial.supported accordingly while leaving temporal/variable
logic (summaryTemporal/summaryVariable) unchanged.
static/src/js/zustand/slices/createProjectSlice.ts (3)

280-316: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Stop the outer flow on the first Harmony 401.

return buildPromise(null) only exits the map callback. Because these requests are launched under Promise.all, one expired token still lets the other Harmony calls and the GraphQL fetch continue. That can fire multiple auth redirects and build a nested /login?...state=/login?... URL instead of a single clean redirect.

🤖 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 `@static/src/js/zustand/slices/createProjectSlice.ts` around lines 280 - 316,
The current map callback returns buildPromise(null) on a Harmony 401, which only
exits the individual callback and doesn't short-circuit Promise.all; change that
so the outer Promise.all rejects immediately by re-throwing the 401 error (or
returning Promise.reject(error)) instead of returning buildPromise(null). Locate
the filteredIds.map async callback in createProjectSlice.ts (the block that
constructs HarmonyCapabilitiesDocumentRequest and calls .search), and replace
the `return buildPromise(null)` inside the `if (error instanceof AxiosError &&
error.response?.status === 401)` branch with `throw error` (or `return
Promise.reject(error)`) so Promise.all rejects and the outer flow stops; keep
the existing zustandState.errors.handleError call intact.

779-787: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard collection before destructuring it.

If a Harmony update arrives after the collection is removed or before it exists, const { selectedAccessMethod, accessMethods } = collection throws before your bailout runs. The generic updateAccessMethod path used to no-op safely for a missing collection; this new branch loses that protection.

Suggested fix
       set((state) => {
         const { collections } = state.project
         const { byId } = collections
         const collection = byId[collectionId]

-        const { selectedAccessMethod, accessMethods } = collection
-
-        // If missing selectedAccessMethod or accessMethods, do no execute
-        if (!selectedAccessMethod || !accessMethods) return
+        if (!collection?.selectedAccessMethod || !collection.accessMethods) return
+
+        const { selectedAccessMethod, accessMethods } = collection
🤖 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 `@static/src/js/zustand/slices/createProjectSlice.ts` around lines 779 - 787,
The code destructures selectedAccessMethod and accessMethods from collection
without first ensuring collection exists, so if the collection was removed the
destructure will throw; update the setter to first check that const collection =
byId[collectionId] is truthy (or bail out if undefined) before doing const {
selectedAccessMethod, accessMethods } = collection — e.g., guard collection (and
keep the existing early return for missing selectedAccessMethod/accessMethods)
so the updateAccessMethod branch / set((state) => { ... }) no-ops safely when
the collection is absent.

795-850: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Rebuild the Harmony method instead of manually syncing a subset.

After recomputing derivedHarmonyState, this only copies flags and selected values back into store state. variables, hierarchyMappings, and keywordMappings stay from the old build even though buildHarmony derives them from the new Harmony state. A selection change that narrows the active service set can therefore leave stale Harmony variable options in the store/UI.

Suggested direction
+import { buildHarmony } from '../../util/accessMethods/buildAccessMethods/buildHarmony'
...
-          // Recalculate the derived state
-          harmonyMethod.derivedHarmonyState = getDerivedHarmonyState(
-            updatedSelections,
-            harmonyMethod.harmonyCapabilitiesDocument
-          )
-
-          const { capabilities } = harmonyMethod.derivedHarmonyState
-
-          if (!capabilities) return
-
-          const {
-            temporalSubset,
-            spatialSubset,
-            outputFormats,
-            variableSubset,
-            concatenate,
-            reproject
-          } = capabilities
-
-          // Use the derived harmony state to set what is enabled or disabled
-          harmonyMethod.enableTemporalSubsetting = updatedSelections.temporalSubset || false
-          ...
-          harmonyMethod.isConcatenationDisabled = concatenate.disabled
+          const rebuiltHarmonyMethod = buildHarmony(
+            harmonyMethod.harmonyCapabilitiesDocument,
+            updatedSelections
+          )
+
+          if (!rebuiltHarmonyMethod) return
+
+          accessMethods[selectedAccessMethod] = rebuiltHarmonyMethod
🤖 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 `@static/src/js/zustand/slices/createProjectSlice.ts` around lines 795 - 850,
The current update manually copies selection flags into harmonyMethod after
computing getDerivedHarmonyState, which leaves variables, hierarchyMappings, and
keywordMappings stale; instead, rebuild the entire Harmony method object using
the new derivedHarmonyState (i.e. call the existing buildHarmony function or the
routine that constructs a fresh harmonyMethod) using updatedSelections and the
new derivedHarmonyState, then replace harmonyMethod with that rebuilt object so
variables, hierarchyMappings, keywordMappings and all availability flags come
from the canonical build rather than piecemeal assignment; ensure you preserve
any necessary identity/refs expected elsewhere (or update callers) and remove
the manual field-by-field syncing that sets enableTemporalSubsetting,
enableSpatialSubsetting, selectedVariables, outputFormatAvailability, etc.
tests/e2e/history/history.spec.js (1)

89-93: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Capabilities route mock is brittle due to exact query-string matching.

This matcher can fail if query params are reordered or additional params are appended, causing flaky e2e behavior.

💡 Suggested fix
-    await page.route(`**/capabilities?collectionId=${conceptId}&version=2`, async (route) => {
-      await route.fulfill({
-        json: { services: [] }
-      })
-    })
+    await page.route('**/capabilities?*', async (route) => {
+      const url = new URL(route.request().url())
+
+      if (
+        url.searchParams.get('collectionId') === conceptId
+        && url.searchParams.get('version') === '2'
+      ) {
+        await route.fulfill({ json: { services: [] } })
+        return
+      }
+
+      await route.continue()
+    })
🤖 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 `@tests/e2e/history/history.spec.js` around lines 89 - 93, The current
page.route matcher uses an exact query-string and is brittle; update the mock in
the tests to match the /capabilities path more robustly by using a looser route
pattern (e.g., '**/capabilities**') and then inspect the actual request URL
inside the handler via route.request().url() or new URL(...). Check searchParams
for collectionId === conceptId and version === '2', call route.fulfill({ json: {
services: [] } }) when it matches, otherwise call route.continue() so other
queries are not incorrectly mocked.
tests/e2e/page_titles/page_titles.spec.js (1)

166-170: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use parameter-based matching for capabilities route to avoid flaky misses.

The exact URL glob is fragile to query-string ordering and optional params.

💡 Suggested fix
-        await page.route('**/capabilities?collectionId=C1443528505-LAADS&version=2', async (route) => {
-          await route.fulfill({
-            json: { services: [] }
-          })
-        })
+        await page.route('**/capabilities?*', async (route) => {
+          const url = new URL(route.request().url())
+
+          if (
+            url.searchParams.get('collectionId') === 'C1443528505-LAADS'
+            && url.searchParams.get('version') === '2'
+          ) {
+            await route.fulfill({ json: { services: [] } })
+            return
+          }
+
+          await route.continue()
+        })
📝 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.

        await page.route('**/capabilities?*', async (route) => {
          const url = new URL(route.request().url())

          if (
            url.searchParams.get('collectionId') === 'C1443528505-LAADS'
            && url.searchParams.get('version') === '2'
          ) {
            await route.fulfill({ json: { services: [] } })
            return
          }

          await route.continue()
        })
🤖 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 `@tests/e2e/page_titles/page_titles.spec.js` around lines 166 - 170, The route
currently matches a full glob including query params which is fragile; change
the page.route registration for the capabilities endpoint to match the base path
(e.g., '**/capabilities') and inside the route handler inspect
route.request().url (or construct a URL object) to check searchParams for
collectionId and version, then call route.fulfill({ json: { services: [] } })
only when those params match; update the handler around the existing page.route
callback and keep using route.fulfill to respond.

Comment thread static/src/js/types/sharedTypes.ts Outdated
Comment thread static/src/js/zustand/slices/__tests__/createProjectSlice.test.ts Outdated
Comment thread static/src/js/zustand/slices/__tests__/createProjectSlice.test.ts Outdated
Comment thread static/src/js/util/__tests__/retrievals.test.js Outdated
Comment thread static/src/js/util/accessMethods/buildAccessMethods/buildHarmony.ts Outdated
Comment thread static/src/js/zustand/slices/createProjectSlice.ts
Comment thread static/src/js/util/determineVariableId.ts
@william-valencia

william-valencia commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

How does renaming "Selected Area:" to "Spatial Constraint:" sound? then similar for "Selected Range:" renamed to "Temporal Constraint:".

@william-valencia

william-valencia commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

Will: Is there a use case where the "Default Format" would disable one of the other options right away or are all options always available on initial load?

Mandy: Default Format means that no conversion was chosen. It will not disable any other options.

reproject: {
/** Flag to indicate if reprojection is supported by the collection */
supported: boolean
supported: SupportedProjection[]

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.

A TODO here might be good or a comment that we do this because we don't currently support interpolation I added EDSC-4686 as a placeholder for that effort for now

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I added a comment about not supporting Interpolation Methods at this time. I don't want to add a TODO here as we will most likely export this as a library before any work is done for Interpolation Methods.

Comment thread static/src/js/components/AccessMethod/AccessMethod.jsx Outdated
Comment thread static/src/js/components/AccessMethod/AccessMethod.jsx Outdated

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.

"None" feels like the same problem we fixed with the output format. I don't think the data can have a projection of "none", it will always have something, so maybe this should be "Default Projection"?

Comment thread static/src/js/components/AccessMethod/__tests__/AccessMethod.test.jsx Outdated
})

describe('when supportsSpatialSubsetting is set to true', () => {
describe('when enableSpatialSubsetting is set to false', () => {

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.

There is no describe block for "when enableSpatialSubsetting is set to true"

Comment thread static/src/js/util/accessMethods/getVariables.ts
Comment thread static/src/js/util/determineVariableId.ts Outdated
Comment on lines +774 to +777
}: {
collectionId: string,
newSelections: UserSelections
}

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.

This type shouldn't be defined here, all of the zustand function types are defined in zustand/types.ts

Comment thread static/src/js/zustand/slices/createProjectSlice.ts Outdated
Comment thread static/src/js/zustand/slices/createProjectSlice.ts Outdated
Comment on lines +771 to +776
updateHarmonySelection: (
{
collectionId,
newMethod
}: UpdateHarmonySelectionParams
) => {

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.

Suggested change
updateHarmonySelection: (
{
collectionId,
newMethod
}: UpdateHarmonySelectionParams
) => {
updateHarmonySelection: ({ collectionId, newMethod }) => {

That param object is already defined as UpdateHarmonySelectionParams in zustand/types.ts

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I see. Because line 902 in zustand/types explicitly says updateHarmonySelection: ({ collectionId, newMethod }: UpdateHarmonySelectionParams) => void, I don't have to say it again here.

@mandyparson mandyparson merged commit 8bb3e99 into feature/EDSC-4638 Jun 23, 2026
13 of 21 checks passed
@mandyparson mandyparson deleted the EDSC-4662 branch June 23, 2026 16:29
mandyparson added a commit that referenced this pull request Jun 24, 2026
… parameters are still valid (#2055)

* EDSC-4662: saving progress

* EDSC-4662: Finalizing changes for customization forms

* EDSC-4662: Performing self-review and Code Rabbit Comments

* EDSC-4662: PR feedback

* EDSC-4662: Changing name

* EDSC-4662: PR Feedback

* EDSC-4662: Fixing variables

* EDSC-4662: fixing update params

* EDSC-4662: Changing 'None' to 'Default Projection'

* EDSC-4662: Changed to 'No Reprojection'
mandyparson added a commit that referenced this pull request Jun 26, 2026
… parameters are still valid (#2055)

* EDSC-4662: saving progress

* EDSC-4662: Finalizing changes for customization forms

* EDSC-4662: Performing self-review and Code Rabbit Comments

* EDSC-4662: PR feedback

* EDSC-4662: Changing name

* EDSC-4662: PR Feedback

* EDSC-4662: Fixing variables

* EDSC-4662: fixing update params

* EDSC-4662: Changing 'None' to 'Default Projection'

* EDSC-4662: Changed to 'No Reprojection'
mandyparson added a commit that referenced this pull request Jul 1, 2026
… parameters are still valid (#2055)

* EDSC-4662: saving progress

* EDSC-4662: Finalizing changes for customization forms

* EDSC-4662: Performing self-review and Code Rabbit Comments

* EDSC-4662: PR feedback

* EDSC-4662: Changing name

* EDSC-4662: PR Feedback

* EDSC-4662: Fixing variables

* EDSC-4662: fixing update params

* EDSC-4662: Changing 'None' to 'Default Projection'

* EDSC-4662: Changed to 'No Reprojection'
mandyparson added a commit that referenced this pull request Jul 1, 2026
… parameters are still valid (#2055)

* EDSC-4662: saving progress

* EDSC-4662: Finalizing changes for customization forms

* EDSC-4662: Performing self-review and Code Rabbit Comments

* EDSC-4662: PR feedback

* EDSC-4662: Changing name

* EDSC-4662: PR Feedback

* EDSC-4662: Fixing variables

* EDSC-4662: fixing update params

* EDSC-4662: Changing 'None' to 'Default Projection'

* EDSC-4662: Changed to 'No Reprojection'
mandyparson added a commit that referenced this pull request Jul 2, 2026
… parameters are still valid (#2055)

* EDSC-4662: saving progress

* EDSC-4662: Finalizing changes for customization forms

* EDSC-4662: Performing self-review and Code Rabbit Comments

* EDSC-4662: PR feedback

* EDSC-4662: Changing name

* EDSC-4662: PR Feedback

* EDSC-4662: Fixing variables

* EDSC-4662: fixing update params

* EDSC-4662: Changing 'None' to 'Default Projection'

* EDSC-4662: Changed to 'No Reprojection'
mandyparson added a commit that referenced this pull request Jul 7, 2026
… parameters are still valid (#2055)

* EDSC-4662: saving progress

* EDSC-4662: Finalizing changes for customization forms

* EDSC-4662: Performing self-review and Code Rabbit Comments

* EDSC-4662: PR feedback

* EDSC-4662: Changing name

* EDSC-4662: PR Feedback

* EDSC-4662: Fixing variables

* EDSC-4662: fixing update params

* EDSC-4662: Changing 'None' to 'Default Projection'

* EDSC-4662: Changed to 'No Reprojection'
mandyparson added a commit that referenced this pull request Jul 7, 2026
* EDSC-4642: As a user, I would like to set temporal, spatial, or output format capabilities and know which parameters are still valid (#2042)

* EDSC-4642: Initial Commit

* EDSC-4642: Editing static.config

* EDSC-4642: Edits to static.config

* EDSC-4642: Updating based on community feedback

* EDSC-4642: Fixing tests for all but AccessMethod

* EDSC-4642: Fixing tests

* EDSC-4642: Adding version to url params

* EDSC-4642: Structural changes to store

* EDSC-4642: A bit of clean up

* EDSC-4642: Cleaning up as per feedback

* EDSC-4642: PR feedback

* EDSC-4661: As a user, I would like to set variables and concatenation and know which parameters are still valid (#2051)

* EDSC-4661: Adding variables and concatenation

* EDSC-4661: Fixing bug found with multiple harmony cap docs

* EDSC-4661: Addressing PR feedback

* EDSC-4661: PR Feedback and cleaning up tests

* EDSC-4661: Lint errors

* EDSC-4661: Fixing tests

* EDSC-4661: Fixing minor env variable

* EDSC-4661: Adding test and comments

* EDSC-4662: As a user, I would like to set reprojection and know which parameters are still valid (#2055)

* EDSC-4662: saving progress

* EDSC-4662: Finalizing changes for customization forms

* EDSC-4662: Performing self-review and Code Rabbit Comments

* EDSC-4662: PR feedback

* EDSC-4662: Changing name

* EDSC-4662: PR Feedback

* EDSC-4662: Fixing variables

* EDSC-4662: fixing update params

* EDSC-4662: Changing 'None' to 'Default Projection'

* EDSC-4662: Changed to 'No Reprojection'

* EDSC-4683: Adding e2e playwright tests (#2062)

* EDSC-4638: Writing playwright tests

* EDSC-4683: Fixing page_titles.spec

* EDSC-4683:PR Feedback

* EDSC-4683: Line fix

* EDSC-4683-testing

* EDSC-4638-testing: removing alert for all but Harmony

* EDSC-4638: Fixing that test

* EDSC-4638: Fixing test coverage

* EDSC-4638: Addressing feedback

* EDSC-4638: lint

* EDSC-4638: Removing another 'no-implicit-any' line
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.

4 participants