Skip to content

KMS-686: Update Keywords in ISO19115 native records#115

Open
htranho wants to merge 28 commits into
mainfrom
KMS-686
Open

KMS-686: Update Keywords in ISO19115 native records#115
htranho wants to merge 28 commits into
mainfrom
KMS-686

Conversation

@htranho

@htranho htranho commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Overview

What is the feature?

Update Keywords in ISO19115 (ISO-MENDS) native records

What is the Solution?

Update keywords in following schemes as they appear in ISO-MENDS records, according to correction requests:

sciencekeywords
locations
platforms
instruments
projects
providers
isotopiccategory
productlevelid

What areas of the application does this impact?

Consumer - Update Keywords in ISO-MENDS native records

Testing

Run:

npx vite-node --config vite.config.js scripts/local/run_metadata_correction_iso-19115_smoke.mjs

The request fixture currently includes the supported UMM-C correction matrix:

  • sciencekeywords
  • locations
  • platforms
  • instruments
  • projects
  • providers
  • isotopiccategory
  • productlevelid

The script prints a JSON summary like:

[metadata-correction-smoke] Applied corrections locally
{
  "fixturePath": "/Users/htranho/repos/kms/scripts/local/fixtures/metadata_correction_service.iso-19115.corrections.json",
  "collectionConceptId": "C1234567222-LOCAL",
  "nativeFormat": "ISO19115",
  "requestedCorrections": 10,
  "appliedCorrections": 10,
  "outputPath": "/Users/htranho/repos/kms/tmp/metadata_correction_service_iso-19115_smoke_output.xml",
  "writeResult": {
    "targetComponent": "cmr-writeback",
    "collectionConceptId": "C1234567222-LOCAL",
    "providerId": null,
    "nativeId": null,
    "nativeFormat": "ISO19115",
    "correctionCount": 10,
    "correctionsAppliedCount": 10,
    "correctedMetadataBytes": 16144,
    "source": "local-smoke",
    "ingestResult": {
      "enabled": false,
      "ingested": false,
      "updated": false
    }
  }
}

The important signals are:

  • nativeFormat is ISO19115
  • requestedCorrections matches the fixture
  • appliedCorrections matches the expected applied count
  • writeResult.targetComponent is cmr-writeback

The corrected XML is written to:

tmp/metadata_correction_service_iso-19115_smoke_output.xml

See scripts/local/fixtures/metadata_correction_service.iso-19115.corrections.json for requested corrections and compare them with result in tmp/metadata_correction_service_iso-19115_smoke_output.xml

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

  • New Features
    • Apply ISO-19115 metadata corrections across science keywords, locations, platforms, instruments, projects, providers, topic categories, and processing levels.
    • Added native-format aliases iso19115 and isosmap for command-line resolution.
    • Introduced an ISO-19115 XML editor and scheme-based keyword editors to update and delete targeted nodes.
  • Bug Fixes
    • Corrections now perform real replace/delete updates with proper cleanup (including removal of emptied containers) and synchronized processing-level edits.
  • Tests
    • Expanded ISO-19115 coverage with new fixtures, a new smoke-test workflow, and expanded editor behavior assertions.
  • Chores
    • Improved shared XML utilities and namespace/XPath-based editing helpers.

@htranho htranho marked this pull request as draft June 30, 2026 15:14
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds ISO 19115 correction processing with shared XML helpers, namespace-aware XML editing, scheme-specific correction wiring, real delegate orchestration, tests, and local smoke coverage.

Changes

ISO 19115 Metadata Correction Pipeline

Layer / File(s) Summary
Shared XML utility helpers
serverless/src/shared/XmlUtils.js, serverless/src/shared/XmlMetadataPathEditor.js, serverless/src/shared/__tests__/XmlUtils.test.js
Adds XML/text helper exports for trimming, keyword normalization, path validation, and namespace extraction, and updates the base editor to import them.
Iso19115MetadataPathEditor class
serverless/src/shared/Iso19115MetadataPathEditor.js, serverless/src/shared/__tests__/Iso19115MetadataPathEditor.test.js
Adds ISO 19115 namespace resolution plus leaf and keyword-block update/delete behavior, with tests covering selection, replacement, deletion, and fallback paths.
ISO 19115 scheme editors
serverless/src/shared/Iso19115DomEditor.js, serverless/src/shared/__tests__/Iso19115DomEditor.test.js
Adds scheme editors for keyword blocks, topic categories, and processing level IDs, plus the ISO 19115 editor factory.
Real correction orchestration
serverless/src/shared/applyIso19115MetadataCorrections.js, serverless/src/shared/__tests__/metadataCorrectionDelegateStubs.test.js
Replaces the stubbed correction response with real editor-based processing and updates the delegate test to assert a live transformation.
Integration tests for correction schemes
serverless/src/shared/__tests__/applyIso19115MetadataCorrections.test.js
Adds end-to-end coverage for missing input handling and replace/delete flows across sciencekeywords, locations, platforms, instruments, projects, isotopiccategory, productlevelid, and providers.
Fixtures and smoke script
scripts/local/fixtures/metadata_correction_service.iso-19115.corrections.json, scripts/local/fixtures/metadata_correction_service.iso-19115.example.xml, scripts/local/run_metadata_correction_iso-19115_smoke.mjs, scripts/local/list_collection_concept_ids_by_native_format.mjs
Adds ISO 19115 fixture data, a local smoke runner, and new native-format aliases for ISO 19115 labels.

Estimated code review effort: 4 (Complex) | ~60 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main ISO19115 keyword update changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description closely follows the template and covers overview, solution, impacted areas, testing, and checklist items, with only minor gaps in attachments and reproduction details.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch KMS-686

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov-commenter

codecov-commenter commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.70968% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 99.76%. Comparing base (c69be49) to head (f024188).
⚠️ Report is 9 commits behind head on main.

Files with missing lines Patch % Lines
...erverless/src/shared/Iso19115MetadataPathEditor.js 98.17% 3 Missing ⚠️
serverless/src/shared/Iso19115DomEditor.js 99.06% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #115      +/-   ##
==========================================
- Coverage   99.81%   99.76%   -0.06%     
==========================================
  Files         223      228       +5     
  Lines        5516     5875     +359     
  Branches     1592     1727     +135     
==========================================
+ Hits         5506     5861     +355     
- Misses         10       13       +3     
- Partials        0        1       +1     

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

@htranho htranho marked this pull request as ready for review June 30, 2026 16:14

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

🧹 Nitpick comments (4)
serverless/src/shared/__tests__/Iso19115DomEditor.test.js (1)

54-67: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Exercise the real platform formatter instead of duplicating it locally.

This test reimplements the formatting logic inside the assertion, so a regression in ISO_19115_SCHEME_EDITORS.platforms would still pass. It would be stronger to capture the config passed to updateBlockNode and invoke replace[0].source.getValue(...) directly.

🤖 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 `@serverless/src/shared/__tests__/Iso19115DomEditor.test.js` around lines 54 -
67, The platform formatting test is duplicating the formatter logic locally
instead of exercising ISO_19115_SCHEME_EDITORS.platforms, so it can miss
regressions. Update the test to capture the config passed into updateBlockNode
and call replace[0].source.getValue(...) directly on the real platform formatter
output. Use the existing ISO_19115_SCHEME_EDITORS.platforms and updateBlockNode
symbols to locate the right assertion path.
serverless/src/shared/__tests__/Iso19115MetadataPathEditor.test.js (1)

138-155: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the XML mutation, not just the boolean result.

This case never inspects the updated gmd:MD_TopicCategoryCode, so a no-op replace implementation would still pass. Adding a DOM assertion here would also help close the remaining uncovered lines in Iso19115MetadataPathEditor.js.

Suggested test tightening
     const result = editor.updateLeafNode(correction, config)
     expect(result).toBe(true)
-    // Verification would depend on the implementation of setElementText
+    const updatedNode = editor.selectNodes('//gmd:MD_TopicCategoryCode')[0]
+    expect(updatedNode.textContent).toBe('updated-value')
🤖 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 `@serverless/src/shared/__tests__/Iso19115MetadataPathEditor.test.js` around
lines 138 - 155, The test for updateLeafNode currently only checks the boolean
return value, so it would pass even if the XML is not actually updated; tighten
it by asserting the mutated DOM state after calling
Iso19115MetadataPathEditor.updateLeafNode, specifically verifying that the
gmd:MD_TopicCategoryCode text content changed as expected via the existing
setElementText path.
serverless/src/shared/applyIso19115MetadataCorrections.js (1)

2-7: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the stub-only JSDoc.

This header still describes the pre-implementation behavior even though the function now performs real mutations and returns stubbed: false.

🤖 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 `@serverless/src/shared/applyIso19115MetadataCorrections.js` around lines 2 -
7, The JSDoc at the top of applyIso19115MetadataCorrections.js still describes
stub-only behavior, but the implementation now performs real mutations and
returns stubbed: false. Update or remove this header so it accurately documents
the current behavior of applyIso19115MetadataCorrections and any related helper
symbols in this module, replacing the outdated “delegate/stub/hand off” wording
with the actual mutation behavior.
serverless/src/shared/__tests__/applyIso19115MetadataCorrections.test.js (1)

7-8: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Exercise the public orchestrator in this suite.

These tests go straight to ISO_19115_SCHEME_EDITORS, so they do not cover applyIso19115MetadataCorrections()'s scheme normalization, unsupported-scheme filtering, or correctionsApplied bookkeeping.

🤖 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 `@serverless/src/shared/__tests__/applyIso19115MetadataCorrections.test.js`
around lines 7 - 8, The tests are bypassing the public orchestrator, so they
miss scheme normalization, unsupported-scheme filtering, and correctionsApplied
tracking. Update applyIso19115MetadataCorrections() test coverage to invoke the
exported orchestrator directly instead of reaching into
ISO_19115_SCHEME_EDITORS, and assert its behavior for normalized schemes,
ignored unsupported schemes, and the correctionsApplied flag while still using
Iso19115MetadataPathEditor through the public flow.
🤖 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 `@scripts/local/list_collection_concept_ids_by_native_format.mjs`:
- Around line 34-36: The unsupported-format hint is out of sync with the format
aliases added in list_collection_concept_ids_by_native_format.mjs. Update the
error suggestion list in the parsing/validation logic that throws for unknown
formats so it includes the new shortcuts alongside dif10, echo10, and umm, using
the same alias symbols defined in the format map (including iso19115 and
isosmap) to keep the hint accurate.

In `@serverless/src/shared/__tests__/applyIso19115MetadataCorrections.test.js`:
- Around line 512-529: The platform delete test is asserting the wrong
serialized keyword text, so it may not catch a regression in
`ISO_19115_SCHEME_EDITORS.platforms` or
`Iso19115MetadataPathEditor.serialize()`. Update the expectation in `should
delete existing platform keyword block correctly` to match the actual fixture
text for AQUA, and keep the TERRA assertion so the test verifies only the
targeted platform keyword block is removed.

In `@serverless/src/shared/__tests__/metadataCorrectionDelegateStubs.test.js`:
- Around line 181-200: The mock XML fixture is missing the gmi namespace binding
even though the root element uses gmi:MI_Metadata, so update the metadata string
in metadataCorrectionDelegateStubs.test.js to declare xmlns:gmi alongside the
existing gmd and gco namespaces. Keep the change localized to the mockPayload in
this test fixture so the XML is well-formed and parsed consistently.

In `@serverless/src/shared/Iso19115DomEditor.js`:
- Around line 111-139: The productlevelid matcher in createProductLevelIdEditor
only uses the processingLevel XPath, but the delete/replace rules also target
processingLevelCode; update the nodeXPath (or matching logic in updateLeafNode
flow) so it matches every location you later mutate, including the
processingLevelCode MD_Identifier path, and keep the existing sync behavior in
the replace entries aligned with that broader match.

In `@serverless/src/shared/Iso19115MetadataPathEditor.js`:
- Around line 166-173: The selection logic in updateBlockNode currently only
uses the first node returned by this.selectNodes(config.nodeXPath), so
corrections targeting a later MD_Keywords block are missed. Update
updateBlockNode to iterate through all nodes returned by selectNodes and apply
the existing matching/delete/update flow to each candidate until a matching node
is found, rather than stopping at [0].

---

Nitpick comments:
In `@serverless/src/shared/__tests__/applyIso19115MetadataCorrections.test.js`:
- Around line 7-8: The tests are bypassing the public orchestrator, so they miss
scheme normalization, unsupported-scheme filtering, and correctionsApplied
tracking. Update applyIso19115MetadataCorrections() test coverage to invoke the
exported orchestrator directly instead of reaching into
ISO_19115_SCHEME_EDITORS, and assert its behavior for normalized schemes,
ignored unsupported schemes, and the correctionsApplied flag while still using
Iso19115MetadataPathEditor through the public flow.

In `@serverless/src/shared/__tests__/Iso19115DomEditor.test.js`:
- Around line 54-67: The platform formatting test is duplicating the formatter
logic locally instead of exercising ISO_19115_SCHEME_EDITORS.platforms, so it
can miss regressions. Update the test to capture the config passed into
updateBlockNode and call replace[0].source.getValue(...) directly on the real
platform formatter output. Use the existing ISO_19115_SCHEME_EDITORS.platforms
and updateBlockNode symbols to locate the right assertion path.

In `@serverless/src/shared/__tests__/Iso19115MetadataPathEditor.test.js`:
- Around line 138-155: The test for updateLeafNode currently only checks the
boolean return value, so it would pass even if the XML is not actually updated;
tighten it by asserting the mutated DOM state after calling
Iso19115MetadataPathEditor.updateLeafNode, specifically verifying that the
gmd:MD_TopicCategoryCode text content changed as expected via the existing
setElementText path.

In `@serverless/src/shared/applyIso19115MetadataCorrections.js`:
- Around line 2-7: The JSDoc at the top of applyIso19115MetadataCorrections.js
still describes stub-only behavior, but the implementation now performs real
mutations and returns stubbed: false. Update or remove this header so it
accurately documents the current behavior of applyIso19115MetadataCorrections
and any related helper symbols in this module, replacing the outdated
“delegate/stub/hand off” wording with the actual mutation behavior.
🪄 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: ec44b374-f19a-4de5-9254-3b11203bc329

📥 Commits

Reviewing files that changed from the base of the PR and between c69be49 and 903f88c.

📒 Files selected for processing (14)
  • scripts/local/fixtures/metadata_correction_service.iso-19115.corrections.json
  • scripts/local/fixtures/metadata_correction_service.iso-19115.example.xml
  • scripts/local/list_collection_concept_ids_by_native_format.mjs
  • scripts/local/run_metadata_correction_iso-19115_smoke.mjs
  • serverless/src/shared/Iso19115DomEditor.js
  • serverless/src/shared/Iso19115MetadataPathEditor.js
  • serverless/src/shared/XmlMetadataPathEditor.js
  • serverless/src/shared/XmlUtils.js
  • serverless/src/shared/__tests__/Iso19115DomEditor.test.js
  • serverless/src/shared/__tests__/Iso19115MetadataPathEditor.test.js
  • serverless/src/shared/__tests__/XmlUtils.test.js
  • serverless/src/shared/__tests__/applyIso19115MetadataCorrections.test.js
  • serverless/src/shared/__tests__/metadataCorrectionDelegateStubs.test.js
  • serverless/src/shared/applyIso19115MetadataCorrections.js

Comment on lines +34 to +36
umm: 'application/vnd.nasa.cmr.umm+json',
iso19115: 'application/iso19115+xml',
isosmap: 'application/iso:smap+xml'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Keep the unsupported-format hint in sync with these aliases.

If a user mistypes one of the new shortcuts, the thrown error still only suggests dif10, echo10, and umm.

Suggested fix
-    `Unsupported format "${formatArg}". Use dif10, echo10, umm, or pass an exact MIME type.`
+    `Unsupported format "${formatArg}". Use dif10, echo10, umm, iso19115, isosmap, or pass an exact MIME type.`
🤖 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 `@scripts/local/list_collection_concept_ids_by_native_format.mjs` around lines
34 - 36, The unsupported-format hint is out of sync with the format aliases
added in list_collection_concept_ids_by_native_format.mjs. Update the error
suggestion list in the parsing/validation logic that throws for unknown formats
so it includes the new shortcuts alongside dif10, echo10, and umm, using the
same alias symbols defined in the format map (including iso19115 and isosmap) to
keep the hint accurate.

Comment on lines +512 to +529
test('should delete existing platform keyword block correctly', () => {
const editor = new Iso19115MetadataPathEditor(mockIso19115)
const correction = {
scheme: 'platforms',
action: 'delete',
oldKeywordObject: { ShortName: 'AQUA' }
}

const config = ISO_19115_SCHEME_EDITORS.platforms
const success = config(editor, correction)

expect(success).toBe(true)

// Verify the specific keyword is gone
const updatedXml = editor.serialize()
expect(updatedXml).not.toContain('AQUA > Earth Observing System, AQUA')
expect(updatedXml).toContain('TERRA > Earth Observing System, TERRA (AM-1)')
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fix the platform delete expectation.

Line 527 checks for AQUA > Earth Observing System, AQUA, but the fixture contains AQUA > Earth Observing System. This test can still pass if the delete path regresses.

Suggested fix
-    expect(updatedXml).not.toContain('AQUA > Earth Observing System, AQUA')
+    expect(updatedXml).not.toContain('AQUA > Earth Observing System')
📝 Committable suggestion

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

Suggested change
test('should delete existing platform keyword block correctly', () => {
const editor = new Iso19115MetadataPathEditor(mockIso19115)
const correction = {
scheme: 'platforms',
action: 'delete',
oldKeywordObject: { ShortName: 'AQUA' }
}
const config = ISO_19115_SCHEME_EDITORS.platforms
const success = config(editor, correction)
expect(success).toBe(true)
// Verify the specific keyword is gone
const updatedXml = editor.serialize()
expect(updatedXml).not.toContain('AQUA > Earth Observing System, AQUA')
expect(updatedXml).toContain('TERRA > Earth Observing System, TERRA (AM-1)')
})
test('should delete existing platform keyword block correctly', () => {
const editor = new Iso19115MetadataPathEditor(mockIso19115)
const correction = {
scheme: 'platforms',
action: 'delete',
oldKeywordObject: { ShortName: 'AQUA' }
}
const config = ISO_19115_SCHEME_EDITORS.platforms
const success = config(editor, correction)
expect(success).toBe(true)
// Verify the specific keyword is gone
const updatedXml = editor.serialize()
expect(updatedXml).not.toContain('AQUA > Earth Observing System')
expect(updatedXml).toContain('TERRA > Earth Observing System, TERRA (AM-1)')
})
🤖 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 `@serverless/src/shared/__tests__/applyIso19115MetadataCorrections.test.js`
around lines 512 - 529, The platform delete test is asserting the wrong
serialized keyword text, so it may not catch a regression in
`ISO_19115_SCHEME_EDITORS.platforms` or
`Iso19115MetadataPathEditor.serialize()`. Update the expectation in `should
delete existing platform keyword block correctly` to match the actual fixture
text for AQUA, and keep the TERRA assertion so the test verifies only the
targeted platform keyword block is removed.

Comment on lines +181 to +200
const mockPayload = `
<gmi:MI_Metadata xmlns:gmd="http://www.isotc211.org/2005/gmd" xmlns:gco="http://www.isotc211.org/2005/gco">
<gmd:descriptiveKeywords>
<gmd:MD_Keywords>
<gmd:keyword>
<gco:CharacterString>EARTH SCIENCE > ATMOSPHERE > AEROSOLS</gco:CharacterString>
</gmd:keyword>
<gmd:type>
<gmd:MD_KeywordTypeCode codeList="http://www.isotc211.org/2005/resources/Codelist/gmxCodelists.xml#MD_KeywordTypeCode" codeListValue="theme">theme</gmd:MD_KeywordTypeCode>
</gmd:type>
<gmd:thesaurusName>
<gmd:CI_Citation>
<gmd:title>
<gco:CharacterString>NASA / GCMD Science Keywords</gco:CharacterString>
</gmd:title>
</gmd:CI_Citation>
</gmd:thesaurusName>
</gmd:MD_Keywords>
</gmd:descriptiveKeywords>
</gmi:MI_Metadata>`

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Declare the gmi namespace in this fixture.

The root element uses gmi: without an xmlns:gmi binding, so this mock XML is malformed and only passes if the parser is lenient.

Suggested fix
-    const mockPayload = `
-      <gmi:MI_Metadata xmlns:gmd="http://www.isotc211.org/2005/gmd" xmlns:gco="http://www.isotc211.org/2005/gco">
+    const mockPayload = `
+      <gmi:MI_Metadata
+        xmlns:gmi="http://www.isotc211.org/2005/gmi"
+        xmlns:gmd="http://www.isotc211.org/2005/gmd"
+        xmlns:gco="http://www.isotc211.org/2005/gco">
📝 Committable suggestion

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

Suggested change
const mockPayload = `
<gmi:MI_Metadata xmlns:gmd="http://www.isotc211.org/2005/gmd" xmlns:gco="http://www.isotc211.org/2005/gco">
<gmd:descriptiveKeywords>
<gmd:MD_Keywords>
<gmd:keyword>
<gco:CharacterString>EARTH SCIENCE > ATMOSPHERE > AEROSOLS</gco:CharacterString>
</gmd:keyword>
<gmd:type>
<gmd:MD_KeywordTypeCode codeList="http://www.isotc211.org/2005/resources/Codelist/gmxCodelists.xml#MD_KeywordTypeCode" codeListValue="theme">theme</gmd:MD_KeywordTypeCode>
</gmd:type>
<gmd:thesaurusName>
<gmd:CI_Citation>
<gmd:title>
<gco:CharacterString>NASA / GCMD Science Keywords</gco:CharacterString>
</gmd:title>
</gmd:CI_Citation>
</gmd:thesaurusName>
</gmd:MD_Keywords>
</gmd:descriptiveKeywords>
</gmi:MI_Metadata>`
const mockPayload = `
<gmi:MI_Metadata
xmlns:gmi="http://www.isotc211.org/2005/gmi"
xmlns:gmd="http://www.isotc211.org/2005/gmd"
xmlns:gco="http://www.isotc211.org/2005/gco">
<gmd:descriptiveKeywords>
<gmd:MD_Keywords>
<gmd:keyword>
<gco:CharacterString>EARTH SCIENCE > ATMOSPHERE > AEROSOLS</gco:CharacterString>
</gmd:keyword>
<gmd:type>
<gmd:MD_KeywordTypeCode codeList="http://www.isotc211.org/2005/resources/Codelist/gmxCodelists.xml#MD_KeywordTypeCode" codeListValue="theme">theme</gmd:MD_KeywordTypeCode>
</gmd:type>
<gmd:thesaurusName>
<gmd:CI_Citation>
<gmd:title>
<gco:CharacterString>NASA / GCMD Science Keywords</gco:CharacterString>
</gmd:title>
</gmd:CI_Citation>
</gmd:thesaurusName>
</gmd:MD_Keywords>
</gmd:descriptiveKeywords>
</gmi:MI_Metadata>`
🤖 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 `@serverless/src/shared/__tests__/metadataCorrectionDelegateStubs.test.js`
around lines 181 - 200, The mock XML fixture is missing the gmi namespace
binding even though the root element uses gmi:MI_Metadata, so update the
metadata string in metadataCorrectionDelegateStubs.test.js to declare xmlns:gmi
alongside the existing gmd and gco namespaces. Keep the change localized to the
mockPayload in this test fixture so the XML is well-formed and parsed
consistently.

Comment thread serverless/src/shared/Iso19115DomEditor.js
Comment thread serverless/src/shared/Iso19115MetadataPathEditor.js Outdated

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

🤖 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 `@serverless/src/shared/__tests__/applyIso19115MetadataCorrections.test.js`:
- Line 337: The inline XML fixtures used in
applyIso19115MetadataCorrections.test.js are missing the gmi namespace, so the
metadataPayload values are malformed. Update the affected test fixtures that
build a gmi:MI_Metadata root to include xmlns:gmi alongside the existing
namespaces, keeping the test data representative of valid ISO 19115 input.
Locate the inline XML strings in the test cases around the metadataPayload
entries and add the missing namespace declaration consistently to both payloads.
🪄 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: cb21a078-0c23-4586-a13f-5e8eb761bdec

📥 Commits

Reviewing files that changed from the base of the PR and between 903f88c and 58df8ec.

📒 Files selected for processing (2)
  • serverless/src/shared/__tests__/Iso19115MetadataPathEditor.test.js
  • serverless/src/shared/__tests__/applyIso19115MetadataCorrections.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • serverless/src/shared/tests/Iso19115MetadataPathEditor.test.js

}
}),

providers: createKeywordBlock('dataCentre', {

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.

The ISO19115 providers mapping looks pointed at the wrong XML structure. Right now Iso19115DomEditor.js (line 175) treats providers as gmd:descriptiveKeywords/gmd:MD_Keywords with codeListValue='dataCentre', but the mapping sheet’s ISO19115 provider rows point to gmd:pointOfContact/gmd:CI_ResponsibleParty and the related distributor/processor/archive responsible-party paths instead. If a record follows the mapped responsible-party structure and does not also carry a dataCentre keyword block, this delegate will skip the provider update entirely for those places.

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.

Added additional paths.

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.

So I think this still might be too broad, for example if the incoming provider correction is for ARCHIVER = NSIDC, this global path will update every CI_ResponsibleParty/organisationName with NSIDC, including unrelated roles like distributor. The workbook maps provider updates through specific role-based paths, so I think this should be constrained to the matching responsible-party locations for that provider role rather than every org-name occurrence in the document.

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.

Since role (example ARCHIVER) is not part of correction object, we find it safer to not touch CI_ResponsibleParty.

* @returns {boolean} True if the operation was successful.
*/
updateBlockNode(correction, config) {
const targetNode = this.selectNodes(config.nodeXPath)[0] || null

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.

It seems like updateBlockNode() only searches the first matching MD_Keywords block because it does this.selectNodes(config.nodeXPath)[0]. If the target keyword lives in a later matching block, thinking we never look at it and the update/delete returns false even though the record contains a valid match. This probably needs to iterate across all matching blocks and stop on the first one that actually contains the keyword.

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.

Fixed.

fieldPath: ({ node, editor }) => (editor.selectNodes('./gmx:Anchor', node).length > 0 ? 'gmx:Anchor' : 'gco:CharacterString'),
source: {
type: 'computed',
getValue: getValue || (({ correction }) => fieldKeys.map((k) => correction.newKeywordObject[k]).filter(Boolean).join(' > '))

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.

The full-path ISO serializer is collapsing empty hierarchy slots with .filter(Boolean). The mapping html page says on ISO write that if an intermediate keyword is missing but a later keyword exists, we should fill the gap with NONE rather than removing the empty segment.

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.

Added 'NONE' for missing levels

@cgokey cgokey left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Only a few comments, overall looks good.


const context = path.startsWith('//') ? this.document : matchingNode
const relativePath = path.startsWith('//') ? path : `./${path}`;
[fieldNode] = this.selectNodes(relativePath, context)

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.

Take a closer look at this, think it is only updating the first match here on this destructure.

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.

Fixed

// Clean up synchronized paths globally first
if (config.replace) {
config.replace
.filter((replConfig) => typeof replConfig.fieldPath === 'string' && replConfig.fieldPath.startsWith('//'))

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.

looks like delete iterates every node returned and removes them all. With the provider config’s //gmd:CI_ResponsibleParty/gmd:organisationName/gco:CharacterString, deleting one matched provider keyword would also remove unrelated responsible-party organization names elsewhere in the record. This seems too broad for provider cleanup and probably needs to be constrained to the mapped ISO target nodes?

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.

Now constrained to the mapped ISO target nodes.

}
),

platforms: createKeywordBlock('platform', {

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.

The platforms, instruments, and projects mappings currently only update the gmd:descriptiveKeywords/gmd:MD_Keywords text. The mapping workbook calls out additional ISO write targets for these values in the acquisition/identifier structures as well (for example platform rows, instrument row, and project rows). As written, a correction can update the visible keyword block while leaving the corresponding identifier/code/description fields stale, so I think these mappings are only partially implemented right 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.

Now added paths to update the Acquisition nodes.

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

🤖 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 `@serverless/src/shared/__tests__/Iso19115MetadataPathEditor.test.js`:
- Around line 402-445: The current test in Iso19115MetadataPathEditor.test.js
only covers duplicate gco:CharacterString nodes within a single gmd:keyword, not
multiple matching keyword blocks. Update the fixture in updateBlockNode to
include two separate matching gmd:keyword elements (or rename the test to
reflect the narrower behavior) so it actually verifies how updateBlockNode
handles multiple matches, especially since the implementation uses .find() in
Iso19115MetadataPathEditor.
🪄 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: 253224b0-2486-41b0-98c7-17ac74c80e0c

📥 Commits

Reviewing files that changed from the base of the PR and between aa21d28 and 8c40e63.

📒 Files selected for processing (2)
  • serverless/src/shared/Iso19115MetadataPathEditor.js
  • serverless/src/shared/__tests__/Iso19115MetadataPathEditor.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • serverless/src/shared/Iso19115MetadataPathEditor.js

Comment thread serverless/src/shared/__tests__/Iso19115MetadataPathEditor.test.js
if (isPlatformOrInstrument && oldShortName && config.replace) {
// Find all MD_Identifier nodes that match this platform/instrument
const allIdentifiers = this.selectNodes('//gmd:MD_Identifier', this.document)
const identifiersToDelete = allIdentifiers.filter((identifier) => {

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.

The acquisition cleanup here is matching identifiers by gmd:code text alone, but the ISO mapping is narrower than that. For platform/instrument sync we should also require the mapped gmd:codeSpace value, otherwise an unrelated acquisition identifier with the same short name can be updated or deleted too. Example: if one identifier is the UMM platform short-name field and another different identifier also happens to have code AQUA, this logic will treat both as the same target.

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.

Fixed.

]
}),

projects: createKeywordBlock('project', {

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.

The project mapping has one more addition field to update. This code updates the project keyword block, but it does not also sync the mapped acquisition-side gmi:MI_Operation identifier fields from the workbook. Example: if MEASURES > Old Description becomes MEASURES-1 > New Description, the keyword block changes but MI_Operation can still be left with code MEASURES and the old description.

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.

Updated, added new paths

return LongName ? `${ShortName} > ${LongName}` : ShortName
},
// Sync with acquisition information section (gmi:MI_Operation)
additionalPaths: [

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 adds project sync into gmi:MI_Operation on replace, but I don’t see symmetric cleanup for delete in Iso19115MetadataPathEditor. That means we can remove the MD_Keywords project entry while leaving the synced acquisition operation values behind. Can we either add matching delete cleanup?

node,
matchingNode: this.findMatchingNode(node, correction, config)
}))
.find((data) => data.matchingNode !== null)

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.

Should this also guard undefined? findMatchingNode() appears to return undefined on a miss, so !== null may still pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants