From ada6adfae4e537c451c9349e8173465fb83ec902 Mon Sep 17 00:00:00 2001
From: PatchesMaps <patrick.moulden@gmail.com>
Date: Thu, 31 Oct 2024 15:08:32 -0400
Subject: [PATCH 1/2] Refactor layer metadata retrieval and rendering

- Update features.json to include cmrVisualizationsUrl
- Modify getVisMetadata.js to fetch metadata from cmrVisualizationsUrl if available
- Update renderSplitTitle.js and selectors.js to handle title and subtitle properties in different cases
---
 config/default/common/features.json           |  1 +
 tasks/build-options/getVisMetadata.js         | 72 +++++++++++++------
 .../layer/product-picker/renderSplitTitle.js  |  3 +-
 web/js/modules/layers/selectors.js            |  4 +-
 4 files changed, 57 insertions(+), 23 deletions(-)

diff --git a/config/default/common/features.json b/config/default/common/features.json
index 354b3c61bd..7f9b092f73 100644
--- a/config/default/common/features.json
+++ b/config/default/common/features.json
@@ -11,6 +11,7 @@
         "googleTagManager": true,
         "vismetadata": {
             "url": "https://gibs.earthdata.nasa.gov/layer-metadata/v1.0/",
+            "cmrVisualizationsUrl": "https://cmr.uat.earthdata.nasa.gov/search/visualizations",
             "daacMap": {
                 "ASIPS": "Atmosphere SIPS",
                 "GES_DISC":	"GES DISC",
diff --git a/tasks/build-options/getVisMetadata.js b/tasks/build-options/getVisMetadata.js
index 88f0da4b10..5364dd0ced 100644
--- a/tasks/build-options/getVisMetadata.js
+++ b/tasks/build-options/getVisMetadata.js
@@ -3,6 +3,7 @@ const path = require('path')
 const yargs = require('yargs')
 const console = require('console')
 const axios = require('axios').default
+const xml2js = require('xml2js')
 
 const prog = path.basename(__filename)
 
@@ -26,6 +27,12 @@ const options = yargs
     type: 'string',
     description: 'layer-metadata/all.json file'
   })
+  .option('mode', {
+    demandOption: true,
+    alias: 'm',
+    type: 'string',
+    description: 'mode'
+  })
   .epilog('Creates a layer-metadata file containing all layers')
 
 const { argv } = options
@@ -56,7 +63,7 @@ if (fs.existsSync(layerOrderFile)) {
 const outputFile = argv.layerMetadata
 
 const metadataConfig = features.features.vismetadata
-const url = metadataConfig.url
+const { url, cmrVisualizationsUrl } = metadataConfig
 const daacMap = metadataConfig.daacMap || {}
 const layerMetadata = {}
 
@@ -104,6 +111,7 @@ const skipLayers = [
 // NOTE: Only using these properties at this time
 const useKeys = [
   'conceptIds',
+  'ConceptIds',
   'dataCenter',
   'daynight',
   'orbitTracks',
@@ -111,17 +119,21 @@ const useKeys = [
   'ongoing',
   'layerPeriod',
   'title',
-  'subtitle'
+  'Title',
+  'subtitle',
+  'Subtitle',
+  'What'
 ]
 
-async function main (url) {
+async function main (url, cmrVisualizationsUrl) {
   layerOrder = layerOrder.layerOrder
   layerOrder = layerOrder.filter(x => !skipLayers.includes(x))
 
   console.warn(`${prog}: Fetching ${layerOrder.length} layer-metadata files`)
   for (const layerId of layerOrder) {
     if (!layerId.includes('_STD') && !layerId.includes('_NRT')) {
-      await getMetadata(layerId, url)
+      if (argv.mode === 'verbose') console.warn(`${prog}: Fetching metadata for ${layerId}`)
+      await getMetadata(layerId, url, cmrVisualizationsUrl)
     }
   }
 
@@ -138,10 +150,11 @@ async function main (url) {
 }
 
 async function getDAAC (metadata) {
-  if (!Array.isArray(metadata.conceptIds) || !metadata.conceptIds.length) {
+  const conceptIds = metadata.conceptIds || metadata.ConceptIds
+  if (!Array.isArray(conceptIds) || !conceptIds.length) {
     return metadata
   }
-  for (const collection of metadata.conceptIds) {
+  for (const collection of conceptIds) {
     const origDataCenter = collection.dataCenter
     const dataCenter = daacMap[origDataCenter]
     if (!dataCenter) {
@@ -157,36 +170,55 @@ async function getDAAC (metadata) {
   return metadata
 }
 
-async function getMetadata (layerId, baseUrl, count) {
+async function getMetadata (layerId, baseUrl, ummVisUrl, count) {
   if (count) console.warn(`retry #${count} for ${layerId}`)
-  return axios({
-    method: 'get',
-    url: `${baseUrl}${layerId}.json`,
-    responseType: 'json',
-    timeout: 10000
-  }).then(async (response) => {
-    const metadata = response.data
+  const searchReq = await fetch(`${ummVisUrl}?identifier=${layerId}`, { signal: AbortSignal.timeout(10000) })
+  const searchText = await searchReq?.text?.() || ''
+  const parser = new xml2js.Parser()
+  const searchJson = await parser.parseStringPromise(searchText)
+  const location = searchJson?.results?.references?.[0]?.reference?.[0]?.location?.[0]
+  if (location) {
+    const ummVisReq = await fetch(location, { signal: AbortSignal.timeout(10000) })
+    const metadata = await ummVisReq.json()
     layerMetadata[layerId] = await getDAAC(metadata)
     let metadataKeys = Object.keys(layerMetadata[layerId])
     metadataKeys = metadataKeys.filter(x => !useKeys.includes(x))
     for (const key of metadataKeys) {
       delete layerMetadata[layerId][key]
     }
-  }).catch((error) => {
-    handleException(error, layerId, url, count)
-  })
+    if (argv.mode === 'verbose') console.warn(layerMetadata[layerId])
+  } else {
+    return axios({
+      method: 'get',
+      url: `${baseUrl}${layerId}.json`,
+      responseType: 'json',
+      timeout: 10000
+    }).then(async (response) => {
+      const metadata = response.data
+      layerMetadata[layerId] = await getDAAC(metadata)
+      let metadataKeys = Object.keys(layerMetadata[layerId])
+      metadataKeys = metadataKeys.filter(x => !useKeys.includes(x))
+      for (const key of metadataKeys) {
+        delete layerMetadata[layerId][key]
+      }
+      if (argv.mode === 'verbose') console.warn(layerMetadata[layerId])
+    }).catch((error) => {
+      if (argv.mode === 'verbose') console.warn(`\n ${prog} WARN: Unable to fetch ${layerId} ${error}`)
+      handleException(error, layerId, url, ummVisUrl, count)
+    })
+  }
 }
 
-async function handleException (error, layerId, url, count) {
+async function handleException (error, layerId, url, ummVisUrl, count) {
   if (!count) count = 0
   count++
   if (count <= 5) {
-    await getMetadata(layerId, url, count)
+    await getMetadata(layerId, url, ummVisUrl, count)
   } else {
     console.warn(`\n ${prog} WARN: Unable to fetch ${layerId} ${error}`)
   }
 }
 
-main(url).catch((err) => {
+main(url, cmrVisualizationsUrl).catch((err) => {
   console.error(err.stack)
 })
diff --git a/web/js/components/layer/product-picker/renderSplitTitle.js b/web/js/components/layer/product-picker/renderSplitTitle.js
index 6707e6b798..bfd4a4b49c 100644
--- a/web/js/components/layer/product-picker/renderSplitTitle.js
+++ b/web/js/components/layer/product-picker/renderSplitTitle.js
@@ -9,7 +9,8 @@ import { getOrbitTrackTitle } from '../../../modules/layers/util';
  */
 export default function RenderSplitLayerTitle(props) {
   const { layer } = props;
-  const { title, subtitle } = layer;
+  const title = layer.title || layer.Title;
+  const subtitle = layer.subtitle || layer.Subtitle;
   const layerIsOrbitTrack = layer.layergroup === 'Orbital Track';
   const layerTitle = !layerIsOrbitTrack ? title : `${title} (${getOrbitTrackTitle(layer, false)})`;
   let splitIdx;
diff --git a/web/js/modules/layers/selectors.js b/web/js/modules/layers/selectors.js
index ab3e9b4304..e7443410ef 100644
--- a/web/js/modules/layers/selectors.js
+++ b/web/js/modules/layers/selectors.js
@@ -647,8 +647,8 @@ export function getTitles(config, layerId, projId) {
       tags = forProj.tags;
     }
     const forLayer = config.layers[layerId];
-    title = title || forLayer.title || `[${layerId}]`;
-    subtitle = subtitle || forLayer.subtitle || '';
+    title = title || forLayer.title || forLayer.Title || `[${layerId}]`;
+    subtitle = subtitle || forLayer.subtitle || forLayer.Subtitle || '';
     tags = tags || forLayer.tags || '';
     return {
       title,

From 255a8304a5c402b66d013c6aa817f73cb2ca491b Mon Sep 17 00:00:00 2001
From: PatchesMaps <patrick.moulden@gmail.com>
Date: Tue, 5 Nov 2024 13:03:01 -0500
Subject: [PATCH 2/2] Refactor getVisMetadata.js to convert keys to camelCase

---
 tasks/build-options/getVisMetadata.js | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/tasks/build-options/getVisMetadata.js b/tasks/build-options/getVisMetadata.js
index 5364dd0ced..d0c5f04655 100644
--- a/tasks/build-options/getVisMetadata.js
+++ b/tasks/build-options/getVisMetadata.js
@@ -29,7 +29,7 @@ const options = yargs
   })
   .option('mode', {
     demandOption: true,
-    alias: 'm',
+    alias: 'mo',
     type: 'string',
     description: 'mode'
   })
@@ -186,6 +186,15 @@ async function getMetadata (layerId, baseUrl, ummVisUrl, count) {
     for (const key of metadataKeys) {
       delete layerMetadata[layerId][key]
     }
+    // Convert keys to camelCase
+    for (const key in layerMetadata[layerId]) {
+      const firstUppercaseCharacter = key.match(/^[A-Z]/g)?.[0]
+      if (firstUppercaseCharacter) {
+        const newKey = key.replace(firstUppercaseCharacter, firstUppercaseCharacter.toLowerCase())
+        layerMetadata[layerId][newKey] = layerMetadata[layerId][key]
+        delete layerMetadata[layerId][key]
+      }
+    }
     if (argv.mode === 'verbose') console.warn(layerMetadata[layerId])
   } else {
     return axios({