diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 31b74f3..fb0de5b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -256,7 +256,7 @@ jobs: run: aws s3 sync ${{steps.download.outputs.download-path}} s3://${{ env.SWODLR_UI_BUCKET }} --delete - name: Publish UMM-T with new version - uses: podaac/cmr-umm-updater@0.5.0 + uses: podaac/cmr-umm-updater@0.6.0 if: | needs.build.outputs.deploy_env == 'UAT' || needs.build.outputs.deploy_env == 'OPS' @@ -269,6 +269,7 @@ jobs: disable_removal: 'true' umm_type: 'umm-t' use_associations: 'false' + umm_version: '1.2.0' env: LAUNCHPAD_TOKEN_SIT: ${{secrets.LAUNCHPAD_TOKEN_SIT}} LAUNCHPAD_TOKEN_UAT: ${{secrets.LAUNCHPAD_TOKEN_UAT}} diff --git a/cmr/ops_swodlr_cmr_umm_t.json b/cmr/ops_swodlr_cmr_umm_t.json index 5ff615b..e6968fa 100644 --- a/cmr/ops_swodlr_cmr_umm_t.json +++ b/cmr/ops_swodlr_cmr_umm_t.json @@ -1,91 +1,90 @@ - { - "Name": "SWODLR", - "LongName": "SWOT On-Demand Level 2 Raster Generation", - "Type": "Web User Interface", - "Version": "#.#.#", - "URL": { - "URLContentType": "DistributionURL", - "Type": "GOTO WEB TOOL", - "URLValue": "http://swodlr.podaac.earthdatacloud.nasa.gov/" + "Name": "SWODLR", + "LongName": "SWOT On-Demand Level 2 Raster Generation", + "Type": "Web User Interface", + "Version": "#.#.#", + "Description": "SWODLR (swaa·dler) is an open-source software system developed to generate custom Level 2 raster data products for the SWOT mission. It provides an Application Programming Interface (API) and Graphical User Interface (GUI) that allows end-users to provide custom configurations to generate on-demand raster data products from underlying standard data products (PIXC, PIXCVec).", + "ToolKeywords": [ + { + "ToolCategory": "EARTH SCIENCE SERVICES", + "ToolTopic": "DATA ANALYSIS AND VISUALIZATION", + "ToolTerm": "DATA VISUALIZATION" }, - "Description": "SWODLR (swaa·dler) is an open-source software system developed to generate custom Level 2 raster data products for the SWOT mission. It provides an Application Programming Interface (API) and Graphical User Interface (GUI) that allows end-users to provide custom configurations to generate on-demand raster data products from underlying standard data products (PIXC, PIXCVec).", - "ToolKeywords" : [ - { - "ToolCategory": "EARTH SCIENCE SERVICES", - "ToolTopic": "DATA ANALYSIS AND VISUALIZATION", - "ToolTerm": "DATA VISUALIZATION" - }, - { - "ToolCategory": "EARTH SCIENCE SERVICES", - "ToolTopic": "DATA MANAGEMENT/DATA HANDLING", - "ToolTerm": "SUBSETTING/SUPERSETTING", - "ToolSpecificTerm": "SPATIAL SUBSETTING" - }, - { - "ToolCategory": "EARTH SCIENCE SERVICES", - "ToolTopic": "DATA MANAGEMENT/DATA HANDLING", - "ToolTerm": "SUBSETTING/SUPERSETTING", - "ToolSpecificTerm": "VARIABLE SUBSETTING" - }, - { - "ToolCategory": "EARTH SCIENCE SERVICES", - "ToolTopic": "DATA ANALYSIS AND VISUALIZATION", - "ToolTerm": "GEOGRAPHIC INFORMATION SYSTEMS", - "ToolSpecificTerm": "WEB-BASED GEOGRAPHIC INFORMATION SYSTEMS" - }, - { - "ToolCategory": "EARTH SCIENCE SERVICES", - "ToolTopic": "DATA MANAGEMENT/DATA HANDLING", - "ToolTerm": "DATA ACCESS/RETRIEVAL" - } - ], - "PotentialAction": { - "Type": "SearchAction", - "Target": { - "Type": "EntryPoint", - "ResponseContentType": [ - "text/html" - ], - "UrlTemplate": "http://swodlr.podaac.earthdatacloud.nasa.gov/l={+layers}&ve={+bbox}&d={+date}", - "Description": "SWODLR (swaa·dler) is an open-source software system developed to generate custom Level 2 raster data products for the SWOT mission. It provides an Application Programming Interface (API) and Graphical User Interface (GUI) that allows end-users to provide custom configurations to generate on-demand raster data products from underlying standard data products (PIXC, PIXCVec).", - "HttpMethod": [ - "GET" - ] - }, - "QueryInput": [ - { - "ValueName": "layers", - "Description": "A comma-separated list of visualization layer ids, as defined by GIBS. These layers will be portrayed on the web application", - "ValueRequired": true, - "ValueType": "https://wiki.earthdata.nasa.gov/display/GIBS/GIBS+API+for+Developers#GIBSAPIforDevelopers-LayerNaming" - }, - { - "ValueName": "date", - "Description": "A UTC ISO DateTime. The layers portrayed will correspond to this date.", - "ValueRequired": false, - "ValueType": "https://schema.org/startDate" - }, - { - "ValueName": "bbox", - "Description": "A spatial bounding box that will set the spatial extent of the portrayed layers. The first point is the lower corner, the second point is the upper corner. A box is expressed as two points separated by a space character.", - "ValueRequired": false, - "ValueType": "https://schema.org/box" - } - ] + { + "ToolCategory": "EARTH SCIENCE SERVICES", + "ToolTopic": "DATA MANAGEMENT/DATA HANDLING", + "ToolTerm": "SUBSETTING/SUPERSETTING", + "ToolSpecificTerm": "SPATIAL SUBSETTING" }, - "Organizations": [ - { - "Roles": [ - "ORIGINATOR" - ], - "ShortName": "NASA/JPL/PODAAC", - "LongName": "Physical Oceanography Distributed Active Archive Center, Jet Propulsion Laboratory, NASA" - } - ], - "MetadataSpecification": { - "URL": "https://cdn.earthdata.nasa.gov/umm/tool/v1.1.1", - "Name": "UMM-T", - "Version": "1.1.1" + { + "ToolCategory": "EARTH SCIENCE SERVICES", + "ToolTopic": "DATA MANAGEMENT/DATA HANDLING", + "ToolTerm": "SUBSETTING/SUPERSETTING", + "ToolSpecificTerm": "VARIABLE SUBSETTING" + }, + { + "ToolCategory": "EARTH SCIENCE SERVICES", + "ToolTopic": "DATA ANALYSIS AND VISUALIZATION", + "ToolTerm": "GEOGRAPHIC INFORMATION SYSTEMS", + "ToolSpecificTerm": "WEB-BASED GEOGRAPHIC INFORMATION SYSTEMS" + }, + { + "ToolCategory": "EARTH SCIENCE SERVICES", + "ToolTopic": "DATA MANAGEMENT/DATA HANDLING", + "ToolTerm": "DATA ACCESS/RETRIEVAL" } -} \ No newline at end of file + ], + "Organizations": [ + { + "Roles": [ + "ORIGINATOR" + ], + "ShortName": "NASA/JPL/PODAAC", + "LongName": "Physical Oceanography Distributed Active Archive Center, Jet Propulsion Laboratory, NASA" + } + ], + "URL": { + "URLContentType": "DistributionURL", + "Type": "GOTO WEB TOOL", + "URLValue": "http://swodlr.podaac.earthdatacloud.nasa.gov/" + }, + "MetadataSpecification": { + "URL": "https://cdn.earthdata.nasa.gov/umm/tool/v1.2.0", + "Name": "UMM-T", + "Version": "1.2.0" + }, + "PotentialAction": { + "Type": "SearchAction", + "Target": { + "Type": "EntryPoint", + "ResponseContentType": [ + "text/html" + ], + "UrlTemplate": "http://swodlr.podaac.earthdatacloud.nasa.gov/l={+layers}&ve={+bbox}&d={+date}", + "Description": "SWODLR (swaa·dler) is an open-source software system developed to generate custom Level 2 raster data products for the SWOT mission. It provides an Application Programming Interface (API) and Graphical User Interface (GUI) that allows end-users to provide custom configurations to generate on-demand raster data products from underlying standard data products (PIXC, PIXCVec).", + "HttpMethod": [ + "GET" + ] + }, + "QueryInput": [ + { + "ValueName": "layers", + "Description": "A comma-separated list of visualization layer ids, as defined by GIBS. These layers will be portrayed on the web application", + "ValueRequired": true, + "ValueType": "https://wiki.earthdata.nasa.gov/display/GIBS/GIBS+API+for+Developers#GIBSAPIforDevelopers-LayerNaming" + }, + { + "ValueName": "date", + "Description": "A UTC ISO DateTime. The layers portrayed will correspond to this date.", + "ValueRequired": false, + "ValueType": "https://schema.org/startDate" + }, + { + "ValueName": "bbox", + "Description": "A spatial bounding box that will set the spatial extent of the portrayed layers. The first point is the lower corner, the second point is the upper corner. A box is expressed as two points separated by a space character.", + "ValueRequired": false, + "ValueType": "https://schema.org/box" + } + ] + } +} diff --git a/cmr/sit_swodlr_cmr_umm_t.json b/cmr/sit_swodlr_cmr_umm_t.json index 8b41854..1011ff4 100644 --- a/cmr/sit_swodlr_cmr_umm_t.json +++ b/cmr/sit_swodlr_cmr_umm_t.json @@ -84,8 +84,8 @@ } ], "MetadataSpecification": { - "URL": "https://cdn.earthdata.nasa.gov/umm/tool/v1.1.1", + "URL": "https://cdn.earthdata.nasa.gov/umm/tool/v1.2.0", "Name": "UMM-T", - "Version": "1.1.1" + "Version": "1.2.0" } -} \ No newline at end of file +} diff --git a/cmr/uat_swodlr_cmr_umm_t.json b/cmr/uat_swodlr_cmr_umm_t.json index 0050391..dd3762c 100644 --- a/cmr/uat_swodlr_cmr_umm_t.json +++ b/cmr/uat_swodlr_cmr_umm_t.json @@ -1,91 +1,90 @@ - { - "Name": "SWODLR", - "LongName": "SWOT On-Demand Level 2 Raster Generation", - "Type": "Web User Interface", - "Version": "#.#.#", - "URL": { - "URLContentType": "DistributionURL", - "Type": "GOTO WEB TOOL", - "URLValue": "http://swodlr.podaac.uat.earthdatacloud.nasa.gov/" + "Name": "SWODLR", + "LongName": "SWOT On-Demand Level 2 Raster Generation", + "Type": "Web User Interface", + "Version": "#.#.#", + "Description": "SWODLR (swaa·dler) is an open-source software system developed to generate custom Level 2 raster data products for the SWOT mission. It provides an Application Programming Interface (API) and Graphical User Interface (GUI) that allows end-users to provide custom configurations to generate on-demand raster data products from underlying standard data products (PIXC, PIXCVec).", + "ToolKeywords": [ + { + "ToolCategory": "EARTH SCIENCE SERVICES", + "ToolTopic": "DATA ANALYSIS AND VISUALIZATION", + "ToolTerm": "DATA VISUALIZATION" }, - "Description": "SWODLR (swaa·dler) is an open-source software system developed to generate custom Level 2 raster data products for the SWOT mission. It provides an Application Programming Interface (API) and Graphical User Interface (GUI) that allows end-users to provide custom configurations to generate on-demand raster data products from underlying standard data products (PIXC, PIXCVec).", - "ToolKeywords" : [ - { - "ToolCategory": "EARTH SCIENCE SERVICES", - "ToolTopic": "DATA ANALYSIS AND VISUALIZATION", - "ToolTerm": "DATA VISUALIZATION" - }, - { - "ToolCategory": "EARTH SCIENCE SERVICES", - "ToolTopic": "DATA MANAGEMENT/DATA HANDLING", - "ToolTerm": "SUBSETTING/SUPERSETTING", - "ToolSpecificTerm": "SPATIAL SUBSETTING" - }, - { - "ToolCategory": "EARTH SCIENCE SERVICES", - "ToolTopic": "DATA MANAGEMENT/DATA HANDLING", - "ToolTerm": "SUBSETTING/SUPERSETTING", - "ToolSpecificTerm": "VARIABLE SUBSETTING" - }, - { - "ToolCategory": "EARTH SCIENCE SERVICES", - "ToolTopic": "DATA ANALYSIS AND VISUALIZATION", - "ToolTerm": "GEOGRAPHIC INFORMATION SYSTEMS", - "ToolSpecificTerm": "WEB-BASED GEOGRAPHIC INFORMATION SYSTEMS" - }, - { - "ToolCategory": "EARTH SCIENCE SERVICES", - "ToolTopic": "DATA MANAGEMENT/DATA HANDLING", - "ToolTerm": "DATA ACCESS/RETRIEVAL" - } - ], - "PotentialAction": { - "Type": "SearchAction", - "Target": { - "Type": "EntryPoint", - "ResponseContentType": [ - "text/html" - ], - "UrlTemplate": "http://swodlr.podaac.uat.earthdatacloud.nasa.gov/l={+layers}&ve={+bbox}&d={+date}", - "Description": "SWODLR (swaa·dler) is an open-source software system developed to generate custom Level 2 raster data products for the SWOT mission. It provides an Application Programming Interface (API) and Graphical User Interface (GUI) that allows end-users to provide custom configurations to generate on-demand raster data products from underlying standard data products (PIXC, PIXCVec).", - "HttpMethod": [ - "GET" - ] - }, - "QueryInput": [ - { - "ValueName": "layers", - "Description": "A comma-separated list of visualization layer ids, as defined by GIBS. These layers will be portrayed on the web application", - "ValueRequired": true, - "ValueType": "https://wiki.earthdata.nasa.gov/display/GIBS/GIBS+API+for+Developers#GIBSAPIforDevelopers-LayerNaming" - }, - { - "ValueName": "date", - "Description": "A UTC ISO DateTime. The layers portrayed will correspond to this date.", - "ValueRequired": false, - "ValueType": "https://schema.org/startDate" - }, - { - "ValueName": "bbox", - "Description": "A spatial bounding box that will set the spatial extent of the portrayed layers. The first point is the lower corner, the second point is the upper corner. A box is expressed as two points separated by a space character.", - "ValueRequired": false, - "ValueType": "https://schema.org/box" - } - ] + { + "ToolCategory": "EARTH SCIENCE SERVICES", + "ToolTopic": "DATA MANAGEMENT/DATA HANDLING", + "ToolTerm": "SUBSETTING/SUPERSETTING", + "ToolSpecificTerm": "SPATIAL SUBSETTING" }, - "Organizations": [ - { - "Roles": [ - "ORIGINATOR" - ], - "ShortName": "NASA/JPL/PODAAC", - "LongName": "Physical Oceanography Distributed Active Archive Center, Jet Propulsion Laboratory, NASA" - } - ], - "MetadataSpecification": { - "URL": "https://cdn.earthdata.nasa.gov/umm/tool/v1.1.1", - "Name": "UMM-T", - "Version": "1.1.1" + { + "ToolCategory": "EARTH SCIENCE SERVICES", + "ToolTopic": "DATA MANAGEMENT/DATA HANDLING", + "ToolTerm": "SUBSETTING/SUPERSETTING", + "ToolSpecificTerm": "VARIABLE SUBSETTING" + }, + { + "ToolCategory": "EARTH SCIENCE SERVICES", + "ToolTopic": "DATA ANALYSIS AND VISUALIZATION", + "ToolTerm": "GEOGRAPHIC INFORMATION SYSTEMS", + "ToolSpecificTerm": "WEB-BASED GEOGRAPHIC INFORMATION SYSTEMS" + }, + { + "ToolCategory": "EARTH SCIENCE SERVICES", + "ToolTopic": "DATA MANAGEMENT/DATA HANDLING", + "ToolTerm": "DATA ACCESS/RETRIEVAL" } -} \ No newline at end of file + ], + "Organizations": [ + { + "Roles": [ + "ORIGINATOR" + ], + "ShortName": "NASA/JPL/PODAAC", + "LongName": "Physical Oceanography Distributed Active Archive Center, Jet Propulsion Laboratory, NASA" + } + ], + "URL": { + "URLContentType": "DistributionURL", + "Type": "GOTO WEB TOOL", + "URLValue": "http://swodlr.podaac.uat.earthdatacloud.nasa.gov/" + }, + "MetadataSpecification": { + "URL": "https://cdn.earthdata.nasa.gov/umm/tool/v1.2.0", + "Name": "UMM-T", + "Version": "1.2.0" + }, + "PotentialAction": { + "Type": "SearchAction", + "Target": { + "Type": "EntryPoint", + "ResponseContentType": [ + "text/html" + ], + "UrlTemplate": "http://swodlr.podaac.uat.earthdatacloud.nasa.gov/l={+layers}&ve={+bbox}&d={+date}", + "Description": "SWODLR (swaa·dler) is an open-source software system developed to generate custom Level 2 raster data products for the SWOT mission. It provides an Application Programming Interface (API) and Graphical User Interface (GUI) that allows end-users to provide custom configurations to generate on-demand raster data products from underlying standard data products (PIXC, PIXCVec).", + "HttpMethod": [ + "GET" + ] + }, + "QueryInput": [ + { + "ValueName": "layers", + "Description": "A comma-separated list of visualization layer ids, as defined by GIBS. These layers will be portrayed on the web application", + "ValueRequired": true, + "ValueType": "https://wiki.earthdata.nasa.gov/display/GIBS/GIBS+API+for+Developers#GIBSAPIforDevelopers-LayerNaming" + }, + { + "ValueName": "date", + "Description": "A UTC ISO DateTime. The layers portrayed will correspond to this date.", + "ValueRequired": false, + "ValueType": "https://schema.org/startDate" + }, + { + "ValueName": "bbox", + "Description": "A spatial bounding box that will set the spatial extent of the portrayed layers. The first point is the lower corner, the second point is the upper corner. A box is expressed as two points separated by a space character.", + "ValueRequired": false, + "ValueType": "https://schema.org/box" + } + ] + } +} diff --git a/package-lock.json b/package-lock.json index a0afe0d..3c2e8b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.1.0-10", + "version": "1.0.0-rc.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.1.0-10", + "version": "1.0.0-rc.8", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", @@ -2900,12 +2900,12 @@ "integrity": "sha512-I7xWjLs2YSVMc5gGx1Z3ZG1lgFpITPndpi8Ku55GeEIKpACCPQNS/OTqQbxgTCfq0Ncvcc+CrFov96itVh6Qvw==" }, "node_modules/@gilbarbara/helpers": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@gilbarbara/helpers/-/helpers-0.9.1.tgz", - "integrity": "sha512-B6q4qruzaurfbpmdGK85SSgnI36pFuJlewTul9hWHUv7u8VGxDwjj8anxSfuPyDZ3ovXF1H6ifCVFHQqRV2+Gg==", + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@gilbarbara/helpers/-/helpers-0.9.2.tgz", + "integrity": "sha512-vrydO6+8jOpzPaJ9Om2Ta6BStbpxBlg7j0uV27NnokG+k6bI95ys7rrw7P4hOcRYajkp+K/XpyLufFUUfYrKTQ==", "dependencies": { "@gilbarbara/types": "^0.2.2", - "is-lite": "^1.2.0" + "is-lite": "^1.2.1" } }, "node_modules/@gilbarbara/types": { @@ -2917,9 +2917,9 @@ } }, "node_modules/@gilbarbara/types/node_modules/type-fest": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.9.0.tgz", - "integrity": "sha512-KS/6lh/ynPGiHD/LnAobrEFq3Ad4pBzOlJ1wAnJx9N4EYoqFhMfLIBjUT2UEx4wg5ZE+cC1ob6DCSpppVo+rtg==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.1.tgz", + "integrity": "sha512-7ZnJYTp6uc04uYRISWtiX3DSKB/fxNQT0B5o1OUeCqiQiwF+JC9+rJiZIDrPrNCLLuTqyQmh4VdQqh/ZOkv9MQ==", "engines": { "node": ">=16" }, @@ -4287,9 +4287,9 @@ } }, "node_modules/@types/leaflet-draw": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/@types/leaflet-draw/-/leaflet-draw-1.0.10.tgz", - "integrity": "sha512-1tV0QW5qAcTCmuZZwH19qngHayLxTE9vyDGicfMoASnrFf0oo3+lFOnn/ZRKSskyHKB7T8s6DTk7Henaq4ueyg==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@types/leaflet-draw/-/leaflet-draw-1.0.11.tgz", + "integrity": "sha512-dyedtNm3aSmnpi6FM6VSl28cQuvP+MD7pgpXyO3Q1ZOCvrJKmzaDq0P3YZTnnBs61fQCKSnNYmbvCkDgFT9FHQ==", "dependencies": { "@types/leaflet": "*" } @@ -4353,9 +4353,9 @@ } }, "node_modules/@types/react-datepicker": { - "version": "4.19.3", - "resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-4.19.3.tgz", - "integrity": "sha512-85F9eKWu9fGiD9r4KVVMPYAdkJJswR3Wci9PvqplmB6T+D+VbUqPeKtifg96NZ4nEhufjehW+SX4JLrEWVplWw==", + "version": "4.19.5", + "resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-4.19.5.tgz", + "integrity": "sha512-tKpuj19p9T4sBQm3Bw13CPuhalo4CFOe/LcSUGJ5z6DmHoiBX3uq33iMKePeSEq7OxyU8O1rh5emAm92nyXZLg==", "dev": true, "dependencies": { "@popperjs/core": "^2.9.2", @@ -13370,9 +13370,9 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "node_modules/proj4": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/proj4/-/proj4-2.9.2.tgz", - "integrity": "sha512-bdyfNmtlWjQN/rHEHEiqFvpTUHhuzDaeQ6Uu1G4sPGqk+Xkxae6ahh865fClJokSGPBmlDOQWWaO6465TCfv5Q==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/proj4/-/proj4-2.10.0.tgz", + "integrity": "sha512-0eyB8h1PDoWxucnq88/EZqt7UZlvjhcfbXCcINpE7hqRN0iRPWE/4mXINGulNa/FAvK+Ie7F+l2OxH/0uKV36A==", "dependencies": { "mgrs": "1.0.0", "wkt-parser": "^1.3.3" @@ -13640,9 +13640,9 @@ } }, "node_modules/react-datepicker": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.24.0.tgz", - "integrity": "sha512-2QUC2pP+x4v3Jp06gnFllxKsJR0yoT/K6y86ItxEsveTXUpsx+NBkChWXjU0JsGx/PL8EQnsxN0wHl4zdA1m/g==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.25.0.tgz", + "integrity": "sha512-zB7CSi44SJ0sqo8hUQ3BF1saE/knn7u25qEMTO1CQGofY1VAKahO8k9drZtp0cfW1DMfoYLR3uSY1/uMvbEzbg==", "dependencies": { "@popperjs/core": "^2.11.8", "classnames": "^2.2.6", @@ -13798,9 +13798,9 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-joyride/node_modules/type-fest": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.9.0.tgz", - "integrity": "sha512-KS/6lh/ynPGiHD/LnAobrEFq3Ad4pBzOlJ1wAnJx9N4EYoqFhMfLIBjUT2UEx4wg5ZE+cC1ob6DCSpppVo+rtg==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.1.tgz", + "integrity": "sha512-7ZnJYTp6uc04uYRISWtiX3DSKB/fxNQT0B5o1OUeCqiQiwF+JC9+rJiZIDrPrNCLLuTqyQmh4VdQqh/ZOkv9MQ==", "engines": { "node": ">=16" }, diff --git a/package.json b/package.json index f31a57d..09ff4eb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.1.0-10", + "version": "1.0.0-rc.8", "private": true, "engines": { "node": ">=18.0.0" diff --git a/scss/custom.scss b/scss/custom.scss index 700b3fb..8ad80de 100644 --- a/scss/custom.scss +++ b/scss/custom.scss @@ -4,7 +4,6 @@ @import "node_modules/bootstrap/scss/mixins"; $theme-colors: ( - // "primary": #2275ac, "primary": red, "secondary": #24B363 ); \ No newline at end of file diff --git a/src/components/app/App.tsx b/src/components/app/App.tsx index 24f26b8..befedca 100644 --- a/src/components/app/App.tsx +++ b/src/components/app/App.tsx @@ -17,6 +17,7 @@ import GranuleSelectionAndConfigurationView from '../sidebar/GranuleSelectionAnd import Joyride from 'react-joyride'; import { deleteProduct } from '../sidebar/actions/productSlice'; import { tutorialSteps } from '../tutorial/tutorialConstants'; +import InteractiveTutorialModal from '../tutorial/InteractiveTutorialModal'; const App = () => { const dispatch = useAppDispatch() @@ -34,10 +35,8 @@ const App = () => { run: startTutorial, steps: tutorialSteps }) - useEffect(() => { setState({...joyride, run: startTutorial }) - }, [startTutorial]); const handleJoyrideCallback = (data: { action: any; index: any; status: any; type: any; step: any; lifecycle: any; }) => { @@ -45,7 +44,7 @@ const App = () => { const stepTarget = step.target if (stepTarget === '#configure-options-breadcrumb' && action === 'update') { navigate(`/customizeProduct/configureOptions${search}`) - } else if (stepTarget === '#configure-options-breadcrumb' && action === 'prev') { + } else if (stepTarget === '#configure-options-breadcrumb' && action === 'prev' && lifecycle === 'complete') { navigate(`/customizeProduct/selectScenes${search}`) } else if (stepTarget === '#my-data-page' && action === 'prev') { navigate(`/customizeProduct/configureOptions${search}`) @@ -60,6 +59,7 @@ const App = () => { dispatch(setStartTutorial(false)) navigate(`/customizeProduct/selectScenes`) } + // TODO: Make condition to load previous page when clicking previous before trying to target component to highlight. Use conditions "stepTarget === '#alert-messages' && action === 'prev' && lifecycle === 'init'" }; useEffect(() => { @@ -68,6 +68,7 @@ const App = () => { if (session !== null) { if (currentUser === null) { dispatch(getCurrentUser()); + // perform check to see if user is authenticated } } else { // navigate to welcome page if user isn't authenticated @@ -118,6 +119,7 @@ const App = () => { , true) } /> , true)}/> + ); } diff --git a/src/components/app/appSlice.ts b/src/components/app/appSlice.ts index 2194e64..b5ffcfa 100644 --- a/src/components/app/appSlice.ts +++ b/src/components/app/appSlice.ts @@ -1,5 +1,5 @@ import { PayloadAction, createAsyncThunk, createSlice } from '@reduxjs/toolkit' -import { PageTypes, UserData } from '../../types/constantTypes' +import { PageTypes } from '../../types/constantTypes' import { CurrentUserData } from '../../types/graphqlTypes' import { Session } from '../../authentication/session'; import { getUserData } from '../../user/userData'; @@ -9,7 +9,8 @@ interface AppState { userAuthenticated: boolean, currentPage: PageTypes, currentUser: CurrentUserData | null, - startTutorial: boolean + startTutorial: boolean, + userHasCorrectEdlPermissions: boolean } export const getCurrentUser = createAsyncThunk('currentUser', async () => { @@ -21,7 +22,8 @@ const initialState: AppState = { userAuthenticated: false, currentPage: 'welcome', currentUser: null, - startTutorial: false + startTutorial: false, + userHasCorrectEdlPermissions: false } export const appSlice = createSlice({ @@ -37,6 +39,9 @@ export const appSlice = createSlice({ setStartTutorial: (state, action: PayloadAction) => { state.startTutorial = action.payload }, + setUserHasCorrectEdlPermissions: (state, action: PayloadAction) => { + state.userHasCorrectEdlPermissions = action.payload + }, }, extraReducers(builder) { builder.addCase(getCurrentUser.fulfilled, (state, action) => { @@ -55,6 +60,6 @@ export const appSlice = createSlice({ }, }); -export const { logoutCurrentUser, setStartTutorial } = appSlice.actions +export const { logoutCurrentUser, setStartTutorial, setUserHasCorrectEdlPermissions } = appSlice.actions export default appSlice.reducer diff --git a/src/components/edl/AuthorizationCodeHandler.tsx b/src/components/edl/AuthorizationCodeHandler.tsx index 30e12be..89203af 100644 --- a/src/components/edl/AuthorizationCodeHandler.tsx +++ b/src/components/edl/AuthorizationCodeHandler.tsx @@ -4,6 +4,46 @@ import { useNavigate, useSearchParams } from "react-router-dom"; import { exchangeAuthenticationCode } from "../../authentication/edl"; import { OAuthTokenExchangeFailed } from "../../authentication/exception"; import { Session } from "../../authentication/session"; +import { spatialSearchCollectionConceptId, spatialSearchResultLimit } from "../../constants/rasterParameterConstants"; +import { setUserHasCorrectEdlPermissions } from "../app/appSlice"; + +export const checkUseHasCorrectEdlPermissions = async () => { + try { + // get session token to use in spatial search query + const session = await Session.getCurrent(); + if (session === null) { + throw new Error('No current session'); + } + const authToken = await session.getAccessToken(); + if (authToken === null) { + throw new Error('Failed to get authentication token'); + } + + const polygonUrlString = '&polygon[]=-49.921875,68.58850924263909,-50.06469726562501,68.56844733448305,-50.06469726562501,68.52223694881727,-49.91638183593751,68.52424806853186,-49.921875,68.58850924263909' + const spatialSearchUrl = `https://cmr.earthdata.nasa.gov/search/granules?collection_concept_id=${spatialSearchCollectionConceptId}${polygonUrlString}&page_size=${spatialSearchResultLimit}` + const userHasCorrectEdlPermissions = await fetch(spatialSearchUrl, { + method: 'GET', + credentials: 'omit', + headers: { + Authorization: `Bearer ${authToken}` + } + }).then(response => response.text()).then(data => { + const parser = new DOMParser(); + const xml = parser.parseFromString(data, "application/xml"); + const userHasCorrectEdlPermissions = parseInt(xml.getElementsByTagName("hits")[0].textContent ?? '0') > 0 + return userHasCorrectEdlPermissions + }) + return userHasCorrectEdlPermissions + } catch (err) { + if (err instanceof Error) { + // return err + return false + } else { + // return 'something happened' + return false + } + } +} export default function AuthorizationCodeHandler(): ReactElement { const dispatch = useAppDispatch(); @@ -22,6 +62,8 @@ export default function AuthorizationCodeHandler(): ReactElement { exchangeAuthenticationCode(code) .then(async () => { + // const userHasCorrectEdlPermissions = await checkUseHasCorrectEdlPermissions() + // dispatch(setUserHasCorrectEdlPermissions(userHasCorrectEdlPermissions)) navigate(`/customizeProduct/selectScenes`); }) .catch((ex) => { @@ -30,7 +72,6 @@ export default function AuthorizationCodeHandler(): ReactElement { } else if (ex instanceof TypeError) { console.debug('Network error') } - // TODO: improve this handling resetAuth(); }); diff --git a/src/components/history/GeneratedProductHistory.tsx b/src/components/history/GeneratedProductHistory.tsx index 0ff7986..8dfbee0 100644 --- a/src/components/history/GeneratedProductHistory.tsx +++ b/src/components/history/GeneratedProductHistory.tsx @@ -8,7 +8,6 @@ import { getUserProducts } from "../../user/userData"; import { useLocation, useNavigate } from "react-router-dom"; const GeneratedProductHistory = () => { - // const generatedProducts = useAppSelector((state) => state.product.generatedProducts) const colorModeClass = useAppSelector((state) => state.navbar.colorModeClass) const { search } = useLocation(); const navigate = useNavigate() @@ -66,7 +65,6 @@ const GeneratedProductHistory = () => { {userProducts.map((generatedProductObject, index) => { const {status, utmZoneAdjust, mgrsBandAdjust, outputGranuleExtentFlag, outputSamplingGridType, rasterResolution, timestamp: dateGenerated, cycle, pass, scene, granules} = generatedProductObject const statusToUse = status[0].state - // const downloadUrl = granules && granules.length !== 0 ? {granules[0].uri.split('/').pop()} : 'N/A' const downloadUrl = granules && granules.length !== 0 ? granules[0].uri.split('/').pop() : 'N/A' const utmZoneAdjustToUse = outputSamplingGridType === 'GEO' ? 'N/A' : utmZoneAdjust const mgrsBandAdjustToUse = outputSamplingGridType === 'GEO' ? 'N/A' : mgrsBandAdjust @@ -106,12 +104,6 @@ const GeneratedProductHistory = () => { } const renderProductHistoryViews = () => { - let viewToShow - // if (userProducts.length === 0) { - // viewToShow = productHistoryAlert() - // } else { - // viewToShow = renderHistoryTable() - // } return (

Generated Products Data

diff --git a/src/components/map/WorldMap.tsx b/src/components/map/WorldMap.tsx index 70e84c9..c7aff76 100644 --- a/src/components/map/WorldMap.tsx +++ b/src/components/map/WorldMap.tsx @@ -1,4 +1,4 @@ -import { MapContainer, Polygon, TileLayer, Tooltip, ZoomControl, useMap, FeatureGroup } from 'react-leaflet' +import { MapContainer, Polygon, TileLayer, Tooltip, ZoomControl, useMap, FeatureGroup, useMapEvent } from 'react-leaflet' import L, { LatLngExpression } from 'leaflet'; import 'leaflet/dist/leaflet.css' import { useAppDispatch, useAppSelector } from '../../redux/hooks' @@ -20,6 +20,18 @@ let DefaultIcon = L.icon({ }); L.Marker.prototype.options.icon = DefaultIcon; +function UpdateMapCenter() { + const dispatch = useAppDispatch() + const mapFocus = useAppSelector((state) => state.product.mapFocus) + + const map = useMapEvent('moveend', () => { + const center = [map.getCenter().lat, map.getCenter().lng] + const zoom = map.getZoom() + if ((mapFocus.center[0] !== center[0] && mapFocus.center[1] !== center[1]) || mapFocus.zoom !== zoom) dispatch(setMapFocus({center, zoom})) + }) + return null +} + const WorldMap = () => { const addedProducts = useAppSelector((state) => state.product.addedProducts) const mapFocus = useAppSelector((state) => state.product.mapFocus) @@ -27,8 +39,8 @@ const WorldMap = () => { const footprintStyleOptions = { color: 'limegreen' } const ChangeView = () => { - const map = useMap(); - map.setView(mapFocus.center as LatLngExpression, mapFocus.zoom); + const map = useMap() + map.setView(mapFocus.center as LatLngExpression, mapFocus.zoom) return null } @@ -65,6 +77,7 @@ const WorldMap = () => { }) return polygonString }).join() + console.log(polygonUrlString) const spatialSearchUrl = `https://cmr.earthdata.nasa.gov/search/granules?collection_concept_id=${spatialSearchCollectionConceptId}${polygonUrlString}&page_size=${spatialSearchResultLimit}` const spatialSearchResponse = await fetch(spatialSearchUrl, { method: 'GET', @@ -75,12 +88,12 @@ const WorldMap = () => { }).then(response => response.text()).then(data => { const parser = new DOMParser(); const xml = parser.parseFromString(data, "application/xml"); + console.log(xml) const references: SpatialSearchResult[] = Array.from(new Set(Array.from(xml.getElementsByTagName("name")).map(nameElement => { return (nameElement.textContent)?.match(`${beforeCPS}([0-9]+(_[0-9]+)+)(${afterCPSR}|${afterCPSL})`)?.[1] }))).map(foundIdString => { const cyclePassSceneStringArray = foundIdString?.split('_').map(id => parseInt(id).toString()) const tileValue = parseInt(cyclePassSceneStringArray?.[2] as string) - // const sceneToUse = String(Math.floor(tileValue / 2)) const sceneToUse = String(Math.floor(tileValue)) return {cycle: cyclePassSceneStringArray?.[0], pass: cyclePassSceneStringArray?.[1], scene : sceneToUse} as SpatialSearchResult }) @@ -115,7 +128,6 @@ const WorldMap = () => { {useLocation().pathname.includes('selectScenes') ? ( @@ -124,7 +136,6 @@ const WorldMap = () => { position="topright" onCreated={(createEvent) => onCreate(createEvent)} onEdited={(editEvent) => onEdit(editEvent)} - // onDeleted={(deleteEvent) => onDelete(deleteEvent)} draw={{ rectangle: false, polyline: false, @@ -142,11 +153,12 @@ const WorldMap = () => { attribution='Esri, Maxar, Earthstar Geographics, and the GIS User Community' maxZoom = {18} /> + {addedProducts.map((productObject, index) => ( - {[
{`Cycle: ${productObject.cycle}`}
,
{`Pass: ${productObject.pass}`}
,
{`Scene: ${productObject.scene}`}
]}
+ {[
{`Cycle: ${productObject.cycle}`}
,
{`Pass: ${productObject.pass}`}
,
{`Scene: ${productObject.scene}`}
]}
))}
diff --git a/src/components/sidebar/CustomizeProductsSidebar.tsx b/src/components/sidebar/CustomizeProductsSidebar.tsx index 32f243e..ee0eff9 100644 --- a/src/components/sidebar/CustomizeProductsSidebar.tsx +++ b/src/components/sidebar/CustomizeProductsSidebar.tsx @@ -8,7 +8,6 @@ import CustomizeProductView from './CustomizeProductView'; import GranuleSelectionView from './GranuleSelectionView'; import { CustomizeProductSidebarProps } from '../../types/constantTypes'; import { ArrowsExpand } from 'react-bootstrap-icons'; -import InteractiveTutorialModal from '../tutorial/InteractiveTutorialModal'; const CustomizeProductsSidebar = (props: CustomizeProductSidebarProps) => { const { mode } = props @@ -63,7 +62,6 @@ const CustomizeProductsSidebar = (props: CustomizeProductSidebarProps) => {
handleResizeClickDown(event)}> handleResizeClickDown(event)}/>
- ); } diff --git a/src/components/sidebar/DeleteGranulesModal.tsx b/src/components/sidebar/DeleteGranulesModal.tsx index 781f215..2ed6d47 100644 --- a/src/components/sidebar/DeleteGranulesModal.tsx +++ b/src/components/sidebar/DeleteGranulesModal.tsx @@ -4,13 +4,33 @@ import { useAppSelector, useAppDispatch } from '../../redux/hooks' import { setShowDeleteProductModalFalse } from './actions/modalSlice' import { Row } from 'react-bootstrap'; import { deleteProduct, setSelectedGranules } from './actions/productSlice'; +import { useSearchParams } from 'react-router-dom'; const GenerateProductsModal = () => { const showDeleteProductModal = useAppSelector((state) => state.modal.showDeleteProductModal) const selectedGranules = useAppSelector((state) => state.product.selectedGranules) const dispatch = useAppDispatch() + const [searchParams, setSearchParams] = useSearchParams() + + const removeCPSFromUrl = (cpsCombosToRemove: string[]) => { + const cyclePassSceneParameters = searchParams.get('cyclePassScene')?.split('-') + if (cyclePassSceneParameters) { + const cyclePassSceneParametersToKeep = cyclePassSceneParameters.filter(cpsCombo => !cpsCombosToRemove.includes(cpsCombo)).join('-') + const currentUrlParameters = Object.fromEntries(searchParams.entries()) + if (cyclePassSceneParametersToKeep.length === 0) { + const {cyclePassScene, ...restOfCurrentUrlParameters} = currentUrlParameters + setSearchParams(restOfCurrentUrlParameters) + } else { + setSearchParams({...currentUrlParameters, cyclePassScene: cyclePassSceneParametersToKeep}) + } + } + } + const handleDelete = () => { dispatch(deleteProduct(selectedGranules)) + // remove url parameters of selectedGranules + removeCPSFromUrl(selectedGranules) + // addSearchParamToCurrentUrlState dispatch(setSelectedGranules([])) // unselect select-all box dispatch(setShowDeleteProductModalFalse()) diff --git a/src/components/sidebar/GranuleSelectionAndConfigurationView.tsx b/src/components/sidebar/GranuleSelectionAndConfigurationView.tsx index 737f637..8822a32 100644 --- a/src/components/sidebar/GranuleSelectionAndConfigurationView.tsx +++ b/src/components/sidebar/GranuleSelectionAndConfigurationView.tsx @@ -1,10 +1,34 @@ import CustomizeProductsSidebar from './CustomizeProductsSidebar'; import { GranuleSelectionAndConfigurationViewProps } from '../../types/constantTypes'; import WorldMap from '../map/WorldMap' +import { setShowTutorialModalTrue, setSkipTutorialTrue } from './actions/modalSlice'; +import { useEffect } from 'react'; +import { useAppDispatch, useAppSelector } from '../../redux/hooks'; +import { checkUseHasCorrectEdlPermissions } from '../edl/AuthorizationCodeHandler'; +import { setUserHasCorrectEdlPermissions } from '../app/appSlice'; const GranuleSelectionAndConfigurationView = (props: GranuleSelectionAndConfigurationViewProps) => { + const dispatch = useAppDispatch() + const skipTutorial = useAppSelector((state) => state.modal.skipTutorial) const {mode} = props + + useEffect(() => { + const fetchData = async () => { + const userHasCorrectEdlPermissions = await checkUseHasCorrectEdlPermissions() + dispatch(setUserHasCorrectEdlPermissions(userHasCorrectEdlPermissions)) + } + + // call the function + fetchData() + }, []) + useEffect(() => { + if (!skipTutorial) { + dispatch(setShowTutorialModalTrue()) + dispatch(setSkipTutorialTrue()) + } + }, []); + return ( <> diff --git a/src/components/sidebar/GranuleSelectionView.tsx b/src/components/sidebar/GranuleSelectionView.tsx index ca17591..c88d913 100644 --- a/src/components/sidebar/GranuleSelectionView.tsx +++ b/src/components/sidebar/GranuleSelectionView.tsx @@ -1,28 +1,14 @@ -import { Button, Col, Row } from 'react-bootstrap'; -import { ArrowReturnRight} from 'react-bootstrap-icons'; import GranuleTable from './GranulesTable'; -import { useAppSelector } from '../../redux/hooks' import GranuleTableAlerts from './GranuleTableAlerts'; -import { useLocation, useNavigate } from 'react-router-dom'; import SpatialSearchOptions from './SpatialSearchOptions'; const GranuleSelectionView = () => { - const addedProducts = useAppSelector((state) => state.product.addedProducts) - const navigate = useNavigate(); - const { search } = useLocation(); - return ( - <> +
-
- - - - - - +
); } diff --git a/src/components/sidebar/GranulesTable.tsx b/src/components/sidebar/GranulesTable.tsx index 3fdad95..4bb8342 100644 --- a/src/components/sidebar/GranulesTable.tsx +++ b/src/components/sidebar/GranulesTable.tsx @@ -5,7 +5,7 @@ import { granuleAlertMessageConstant, granuleSelectionLabels, productCustomizati footprintSearchCollectionConceptId } from '../../constants/rasterParameterConstants'; import { Button, Col, Form, OverlayTrigger, Row, Tooltip, Spinner } from 'react-bootstrap'; import { InfoCircle, Plus, Trash } from 'react-bootstrap-icons'; -import { AdjustType, AdjustValueDecoder, GranuleForTable, GranuleTableProps, InputType, SaveType, SpatialSearchResult, TableTypes, alertMessageInput, allProductParameters, validScene } from '../../types/constantTypes'; +import { AdjustType, AdjustValueDecoder, GranuleForTable, GranuleTableProps, InputType, SaveType, SpatialSearchResult, TableTypes, alertMessageInput, allProductParameters, handleSaveResult, validScene } from '../../types/constantTypes'; import { addProduct, setSelectedGranules, setGranuleFocus, addGranuleTableAlerts, editProduct, addSpatialSearchResults, setWaitingForFootprintSearch, clearGranuleTableAlerts } from './actions/productSlice'; import { setShowDeleteProductModalTrue } from './actions/modalSlice'; import DeleteGranulesModal from './DeleteGranulesModal'; @@ -33,7 +33,7 @@ const GranuleTable = (props: GranuleTableProps) => { const {outputSamplingGridType} = generateProductParameters // search parameters - const [searchParams, setSearchParams] = useSearchParams(); + const [searchParams, setSearchParams] = useSearchParams() // set the default url state parameters useEffect(() => { @@ -44,16 +44,6 @@ const GranuleTable = (props: GranuleTableProps) => { const sceneParamArray = Array.from(new Set(cyclePassSceneParameters.split('-'))) sceneParamArray.forEach((sceneParams, index) => { const splitSceneParams = sceneParams.split('_') - if (splitSceneParams.length > 3) { - // update zone and band adjust values - const zoneAdjustValue = adjustParamDecoder('value', splitSceneParams[3]) - const bandAdjustValue = adjustParamDecoder('value', splitSceneParams[4]) - const productToEdit = addedProducts.find(granuleObj => granuleObj.cycle === splitSceneParams[0] && granuleObj.pass === splitSceneParams[1] && granuleObj.scene === splitSceneParams[2]) - if (productToEdit?.utmZoneAdjust !== zoneAdjustValue || productToEdit?.mgrsBandAdjust !== bandAdjustValue) { - const editedProduct = {...productToEdit, utmZoneAdjust: zoneAdjustValue, mgrsBandAdjust: bandAdjustValue} - dispatch(editProduct(editedProduct as allProductParameters)) - } - } handleSave('urlParameter', sceneParamArray.length, index, splitSceneParams[0], splitSceneParams[1], splitSceneParams[2]) }) } @@ -63,20 +53,25 @@ const GranuleTable = (props: GranuleTableProps) => { dispatch(clearGranuleTableAlerts()) if (spatialSearchResults.length > 0) { let scenesFoundArray: string[] = [] + let addedScenes: string[] = [] const fetchData = async () => { for(let i=0; i { - - scenesFoundArray.push(result) + if(result.savedScenes) { + addedScenes.push(...(result.savedScenes).map(productObject => productObject.granuleId)) + } + scenesFoundArray.push(result.result) }) } + // add parameters + addSearchParamToCurrentUrlState({'cyclePassScene': addedScenes.join('-')}) return scenesFoundArray } // call the function fetchData() - .then((noScenesFoundResult) => { - if(scenesFoundArray.includes('noScenesFound') && !scenesFoundArray.includes('found something')){ + .then((noScenesFoundResults) => { + if(noScenesFoundResults.includes('noScenesFound') && !noScenesFoundResults.includes('found something')){ setSaveGranulesAlert('noScenesFound') } }) @@ -89,16 +84,50 @@ const GranuleTable = (props: GranuleTableProps) => { }, [spatialSearchResults]) const addSearchParamToCurrentUrlState = (newPairsObject: object, remove?: string) => { - const currentSearchParams = Object.fromEntries(searchParams.entries()) - Object.entries(newPairsObject).forEach(pair => { + const currentSearchParams = Object.fromEntries(searchParams.entries()) + const cyclePassSceneParameters = searchParams.get('cyclePassScene') + Object.entries(newPairsObject).forEach(pair => { + if (pair[0] === 'cyclePassScene') { + if(cyclePassSceneParameters !== null) { + // check if cps already exists in cyclePassSceneParameters + const currentCpsUrlSplit = cyclePassSceneParameters.split('-') + const paramsToAddSplit = pair[1].toString().split('-') + // let combinedParamsArray = currentCpsUrlSplit + let newParamsArray: string[] = [] + paramsToAddSplit.forEach((newParam: string) => { + if(!currentCpsUrlSplit.includes(newParam)) { + newParamsArray.push(newParam) + // if cps combo not already in url param, add it + // NOTE FOR WHEN I GET BACK: making sure no duplicates of cps + } + }) + if (newParamsArray.length > 0) { + // if cps without adjust params is in currentCpsUrlSplit and newParamsArray has cps with added adjust params + const newCPSParams: string[] = [] + newParamsArray.forEach(newParam => { + currentCpsUrlSplit.forEach(oldParam => { + const splitOldParam = oldParam.split('_') + if(!newParam.includes(`${splitOldParam[0]}_${splitOldParam[1]}_${splitOldParam[2]}`) && !newCPSParams.includes(oldParam)) { + // remove old param + newCPSParams.push(oldParam) + } + }) + }) + currentSearchParams[pair[0]] = [...newCPSParams, ...newParamsArray].join('-') + } + } else { currentSearchParams[pair[0]] = pair[1].toString() - }) - - // remove unused search param - if (remove) { - delete currentSearchParams[remove] + } + } else { + currentSearchParams[pair[0]] = pair[1].toString() } - setSearchParams(currentSearchParams) + }) + + // remove unused search param + if (remove) { + delete currentSearchParams[remove] + } + setSearchParams(currentSearchParams) } // add granules @@ -159,7 +188,6 @@ const validateSceneAvailability = async (cycleToUse: number, passToUse: number, // check for characters other than integers and one - if (inputValue.includes('-')) { const inputBoundsValue = inputValue.split('-') - // const allInputsValidNumbers = inputBoundsValue.every(inputString => !isNaN(+inputString)) const min: string = inputBoundsValue[0].trim() const max: string = inputBoundsValue[1].trim() const minIsValid = checkInBounds(inputType, min) @@ -270,13 +298,13 @@ const validateSceneAvailability = async (cycleToUse: number, passToUse: number, return cpsValueToReturn } - const handleSave = async (saveType: SaveType, totalRuns: number, index: number, cycleParam?: string, passParam?: string, sceneParam?: string): Promise => { + const handleSave = async (saveType: SaveType, totalRuns: number, index: number, cycleParam?: string, passParam?: string, sceneParam?: string): Promise => { if (saveType === 'manual') dispatch(clearGranuleTableAlerts()) setWaitingForScenesToBeAdded(true) // String(+(stringParam)) is used to remove the leading zeros const cycleToUse = String(+(cycleParam ?? cycle)) const passToUse = String(+(passParam ?? pass)) - const sceneToUse = String(+(sceneParam ?? scene)) + const sceneToUse = (sceneParam ?? scene).split('-').map((sceneValueSplit: string) => String(+sceneValueSplit)).join('-') // check if cycle pass and scene are all within a valid range const validCycle = inputIsValid('cycle', cycleToUse) const validPass = inputIsValid('pass', passToUse) @@ -287,10 +315,10 @@ const validateSceneAvailability = async (cycleToUse: number, passToUse: number, if (!validCycle) setSaveGranulesAlert('invalidCycle') if (!validPass) setSaveGranulesAlert('invalidPass') if (!validScene) setSaveGranulesAlert('invalidScene') - return 'first step' + return {result: 'first step'} } else if (addedProducts.length >= granuleTableLimit) { setSaveGranulesAlert('granuleLimit') - return 'second-step' + return {result: 'second-step'} } else { const granulesToAdd: allProductParameters[] = [] let someGranulesAlreadyAdded = false @@ -336,7 +364,7 @@ const validateSceneAvailability = async (cycleToUse: number, passToUse: number, someGranulesAlreadyAdded = true } }) - if (saveType !== 'spatialSearch') { + if (saveType !== 'spatialSearch' && saveType !== 'urlParameter') { // check if any granules could not be found or they were already added if (someGranulesAlreadyAdded) { setSaveGranulesAlert('alreadyAdded') @@ -364,38 +392,44 @@ const validateSceneAvailability = async (cycleToUse: number, passToUse: number, })) })).then(async productsWithFootprints => { // don't run time range check if granule was manually entered - const productsInTimeRange: allProductParameters[] = [] - const productsNotInTimeRange:allProductParameters[] = [] - productsWithFootprints.forEach(product => { - if (product.inTimeRange){ - delete product.inTimeRange - productsInTimeRange.push(product) - } else if (!product.inTimeRange) { - delete product.inTimeRange - productsNotInTimeRange.push(product) - } - }) - if (productsInTimeRange.length > 0) { - setSaveGranulesAlert('success') - dispatch(addProduct(productsInTimeRange)) + if (saveType === 'manual' || saveType === 'urlParameter') { addSearchParamToCurrentUrlState({'cyclePassScene': cyclePassSceneSearchParams}) - } - if (productsNotInTimeRange.length > 0) { - // set alerts for not in range - setSaveGranulesAlert('notInTimeRange') + if (saveType !== 'urlParameter') { + setSaveGranulesAlert('success') + } + dispatch(addProduct(productsWithFootprints)) + } else { + const productsInTimeRange: allProductParameters[] = [] + const productsNotInTimeRange:allProductParameters[] = [] + productsWithFootprints.forEach(product => { + if (product.inTimeRange){ + delete product.inTimeRange + productsInTimeRange.push(product) + } else if (!product.inTimeRange) { + delete product.inTimeRange + productsNotInTimeRange.push(product) + } + }) + if (productsInTimeRange.length > 0) { + setSaveGranulesAlert('success') + dispatch(addProduct(productsInTimeRange)) + } + if (productsNotInTimeRange.length > 0) { + // set alerts for not in range + setSaveGranulesAlert('notInTimeRange') + } } }) - return 'found something' + return {result: 'found something', savedScenes: granulesToAdd} } else { if (index+1 === totalRuns){ - return 'noScenesFound' + return {result: 'noScenesFound'} } else { - return 'not applicable' + return {result: 'not applicable'} } } }) return validationResult - // return 'third step' } } diff --git a/src/components/sidebar/ProductCustomization.tsx b/src/components/sidebar/ProductCustomization.tsx index 363e7eb..9b7ad43 100644 --- a/src/components/sidebar/ProductCustomization.tsx +++ b/src/components/sidebar/ProductCustomization.tsx @@ -81,7 +81,6 @@ const ProductCustomization = () => { if (showUTMAdvancedOptions) { handleShowUTMAdvancedOptions() } - } else { gridType = "rasterResolutionUTM" searchParamToRemove = "rasterResolutionGEO" @@ -113,13 +112,13 @@ const ProductCustomization = () => { if (outputSamplingGridType === 'utm') { return ( setRasterResolutionUTM(parseInt(event.target.value))}> - {parameterOptionValues.rasterResolutionUTM.values.map(parameterValue => )} + {parameterOptionValues.rasterResolutionUTM.values.map((parameterValue, index) => )} ) } else if (outputSamplingGridType === 'lat/lon') { return ( setRasterResolutionGEO(parseInt(event.target.value))}> - {parameterOptionValues.rasterResolutionGEO.values.map(parameterValue => )} + {parameterOptionValues.rasterResolutionGEO.values.map((parameterValue, index) => )} ) } @@ -162,6 +161,7 @@ const ProductCustomization = () => { type={'radio'} id={`outputSamplingGridTypeGroup-radio-${index}`} onChange={() => setOutputSamplingGridType(value as string, resolutionToUse)} + key={`outputSamplingGridTypeGroup-radio-key-${index}`} /> )} ) @@ -176,6 +176,7 @@ const ProductCustomization = () => { label={'advanced options'} style={{marginTop: '10px'}} disabled={!(outputSamplingGridType === 'utm')} + key={`outputGranuleExtentFlag-switch-key`} /> ) ) @@ -206,6 +207,7 @@ const ProductCustomization = () => { type={'radio'} id={`outputGranuleExtentFlagTypeGroup-radio-${index}`} onChange={() => setOutputGranuleExtentFlag(value)} + key={`outputGranuleExtentFlagTypeGroup-radio-key-${index}`} /> ) })} diff --git a/src/components/sidebar/SpatialSearchOptions.tsx b/src/components/sidebar/SpatialSearchOptions.tsx index 34394af..f6c2b95 100644 --- a/src/components/sidebar/SpatialSearchOptions.tsx +++ b/src/components/sidebar/SpatialSearchOptions.tsx @@ -1,4 +1,4 @@ -import { Col, Row } from 'react-bootstrap'; +import { Alert, Col, Row } from 'react-bootstrap'; import DatePicker from "react-datepicker"; import "react-datepicker/dist/react-datepicker.css"; import { useAppDispatch, useAppSelector } from '../../redux/hooks'; @@ -10,6 +10,7 @@ const SpatialSearchOptions = () => { const colorModeClass = useAppSelector((state) => state.navbar.colorModeClass) const spatialSearchStartDate = useAppSelector((state) => state.product.spatialSearchStartDate) const spatialSearchEndDate = useAppSelector((state) => state.product.spatialSearchEndDate) + const userHasCorrectEdlPermissions = useAppSelector((state) => state.app.userHasCorrectEdlPermissions) const dispatch = useAppDispatch() // set spatial search start and end date if in url params @@ -100,6 +101,17 @@ const SpatialSearchOptions = () => {

+ {userHasCorrectEdlPermissions ? null : + ( + + +

+ Spatial search is not available because the SWOT dataset that allows this feature is not currently publicly available. +

+
+ +
) + } ); } diff --git a/src/components/sidebar/actions/modalSlice.ts b/src/components/sidebar/actions/modalSlice.ts index 0587281..ae3a743 100644 --- a/src/components/sidebar/actions/modalSlice.ts +++ b/src/components/sidebar/actions/modalSlice.ts @@ -1,5 +1,4 @@ import { createSlice } from '@reduxjs/toolkit' -import { allProductParameters } from '../../../types/constantTypes' // Define a type for the slice state interface AddCustomProductModalState { @@ -7,7 +6,6 @@ interface AddCustomProductModalState { showEditProductModal: boolean, showDeleteProductModal: boolean, showGenerateProductModal: boolean, - addedProducts: allProductParameters[], sampleGranuleDataArray: number[], selectedGranules: string[], showTutorialModal: boolean, @@ -20,11 +18,10 @@ const initialState: AddCustomProductModalState = { showEditProductModal: false, showDeleteProductModal: false, showGenerateProductModal: false, - showTutorialModal: true, + showTutorialModal: false, skipTutorial: true, // allProducts: this will be like a 'database' for the local state of all the products added // the key will be cycleId_passId_sceneId and the value will be a 'parameterOptionDefaults' type object - addedProducts: [], sampleGranuleDataArray: [], selectedGranules: [] } diff --git a/src/components/sidebar/actions/productSlice.ts b/src/components/sidebar/actions/productSlice.ts index 7682a9d..a98c05e 100644 --- a/src/components/sidebar/actions/productSlice.ts +++ b/src/components/sidebar/actions/productSlice.ts @@ -45,7 +45,7 @@ const initialState: GranuleState = { spatialSearchResults: [], waitingForSpatialSearch: false, waitingForFootprintSearch: false, - spatialSearchStartDate: (new Date(date.setMonth(date.getMonth() - 1))).toISOString(), + spatialSearchStartDate: (new Date(2022, 11, 16)).toISOString(), spatialSearchEndDate: (new Date()).toISOString() } diff --git a/src/components/sidebar/actions/sidebarSlice.ts b/src/components/sidebar/actions/sidebarSlice.ts index 3c70317..0a1cd5c 100644 --- a/src/components/sidebar/actions/sidebarSlice.ts +++ b/src/components/sidebar/actions/sidebarSlice.ts @@ -77,7 +77,4 @@ export const { setSidebarWidth } = sidebarSlice.actions -// Other code such as selectors can use the imported `RootState` type -// export const selectShowAddProductModal = (state: RootState) => state.addCustomProductModal.showAddProductModal - export default sidebarSlice.reducer \ No newline at end of file diff --git a/src/components/tutorial/tutorialConstants.ts b/src/components/tutorial/tutorialConstants.ts index 215d207..41c93e5 100644 --- a/src/components/tutorial/tutorialConstants.ts +++ b/src/components/tutorial/tutorialConstants.ts @@ -34,7 +34,6 @@ export const tutorialSteps = [ } }, { - // target: "#spatial-search-map", target: '#map-tutorial-target', content: "This map allows you to search for scenes by drawing a search area and will display the footprints of scenes once they have been added to your Added Scenes list.", disableBeacon: true, @@ -43,7 +42,6 @@ export const tutorialSteps = [ options: { zIndex: 1000, primaryColor: '#0d6efd', - // offset: 40, } } }, @@ -116,17 +114,6 @@ export const tutorialSteps = [ } } }, - { - target: "#configure-products-button", - content: "Click the Configure Products button to proceed to the Configure Options page where you can select options for how your selected scenes can be made into custom products.", - disableBeacon: true, - styles: { - options: { - zIndex: 1000, - primaryColor: '#0d6efd', - } - } - }, { target: "#configure-options-breadcrumb", content: "You can also click on the Configure Options tab to proceed to the Configure Options view.", diff --git a/src/components/welcome/Welcome.tsx b/src/components/welcome/Welcome.tsx index c681a0b..75eefc5 100644 --- a/src/components/welcome/Welcome.tsx +++ b/src/components/welcome/Welcome.tsx @@ -73,8 +73,8 @@ const Welcome = () => {
4.
-
Download
-
Download generated products once processing is complete.
+
Download
+
Download generated products once processing is complete.
diff --git a/src/constants/graphqlQueries.ts b/src/constants/graphqlQueries.ts index 6fe47d6..e1c1151 100644 --- a/src/constants/graphqlQueries.ts +++ b/src/constants/graphqlQueries.ts @@ -1,3 +1,5 @@ +import { userProductQueryLimit } from "./rasterParameterConstants" + export const userQuery = ` { currentUser { @@ -27,7 +29,7 @@ export const generateL2RasterProductQuery = ` export const userProductsQuery = ` { currentUser { - products { + products (limit: ${userProductQueryLimit}) { id timestamp cycle diff --git a/src/constants/rasterParameterConstants.ts b/src/constants/rasterParameterConstants.ts index a171c46..fad2e10 100644 --- a/src/constants/rasterParameterConstants.ts +++ b/src/constants/rasterParameterConstants.ts @@ -96,7 +96,6 @@ export const parameterOptionDefaults = { } export const parameterHelp: ParameterHelp = { - // outputGranuleExtentFlag: `There are two sizing options for raster granules: square (128 km x 128 km) or rectangular (256 km x 128 km). The square granule extent utilizes the data from only the specific square scene ID indicated, whereas the rectangular granule extent utilizes the specific square scene ID indicated and data from the two adjacent scene IDs along the SWOT swath. At the very edges of scenes, there is a risk that the pixels SWOT measures will not be aggregated as accurately into the raster product. The rectangular extent addresses this issue and could be most helpful with points of interest near the edges of scenes.`, outputGranuleExtentFlag: `There are two sizing options for raster granules: nonoverlapping square (128 km x 128 km) or overlapping rectangular (256 km x 128 km). The rectangular granule extent is 64 km longer in along-track on both sides of the granule and can be useful for observing areas of interest near the along-track edges of the nonoverlapping granules without the need to stitch sequential granules together.`, outputSamplingGridType: `Specifies the type of the raster sampling grid. It can be either a Universal Transverse Mercator (UTM) grid or a geodetic latitude-longitude grid.`, rasterResolution: `Resolution of the raster sampling grid in units of integer meters for UTM grids and integer arc-seconds for latitude-longitude grids.`, @@ -219,15 +218,10 @@ export const granuleAlertMessageConstant: granuleAlertMessageConstantType = { ] export const spatialSearchResultLimit = 2000 -// export const beforeCPS = '_PIXC_' -// export const afterCPSR = 'R_' -// export const afterCPSL = 'L_' -// export const spatialSearchCollectionConceptId = 'C2799438266-POCLOUD' -// export const spatialSearchCollectionConceptId = 'C2799438271-POCLOUD' - export const beforeCPS = '_x_x_x_' export const afterCPSR = 'F_' export const afterCPSL = 'F_' export const spatialSearchCollectionConceptId = 'C2799438271-POCLOUD' -// export const footprintSearchCollectionConceptId = 'C2799438266-POCLOUD' -export const footprintSearchCollectionConceptId = 'C2799438271-POCLOUD' \ No newline at end of file +export const footprintSearchCollectionConceptId = 'C2799438271-POCLOUD' + +export const userProductQueryLimit = 1000 \ No newline at end of file diff --git a/src/redux/store.ts b/src/redux/store.ts index 84eda7e..66b8e76 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -4,7 +4,6 @@ import productSlice from '../components/sidebar/actions/productSlice' import navbarSlice from '../components/navbar/navbarSlice' import appSlice from '../components/app/appSlice' import sidebarSlice from '../components/sidebar/actions/sidebarSlice' -// ... export const store = configureStore({ reducer: { diff --git a/src/types/constantTypes.ts b/src/types/constantTypes.ts index 66de6a3..b61f34a 100644 --- a/src/types/constantTypes.ts +++ b/src/types/constantTypes.ts @@ -100,7 +100,6 @@ export interface AlertMessageObject { type: string, message: string, variant: "danger" | "success" | "warning", - // timeoutId: ReturnType, tableType: TableTypes } @@ -152,4 +151,9 @@ export interface MapFocusObject { zoom: number } -export type SaveType = 'manual' | 'urlParameter' | 'spatialSearch' \ No newline at end of file +export type SaveType = 'manual' | 'urlParameter' | 'spatialSearch' + +export interface handleSaveResult { + result: string, + savedScenes?: allProductParameters[] +} \ No newline at end of file diff --git a/src/user/userData.ts b/src/user/userData.ts index f3b5558..c75aa95 100644 --- a/src/user/userData.ts +++ b/src/user/userData.ts @@ -78,21 +78,6 @@ export const generateL2RasterProduct = async ( ) => { try { // TODO: why doesn't typescript like when I don't specifiy 2 different objects for variables utm and geo??? - // const variablesToUse: ProductGenerationVariables = { - // cycle: parseInt(cycle), - // pass: parseInt(pass), - // scene: parseInt(scene), - // outputGranuleExtentFlag: Boolean(outputGranuleExtentFlag), - // outputSamplingGridType: 'GEO', - // rasterResolution, - // } - - // // if outputSamplingGridType is lat/lon (UTM) - // if (outputSamplingGridType === 'lat/lon') { - // variablesToUse.utmZoneAdjust = parseInt(utmZoneAdjust) - // variablesToUse.mgrsBandAdjust = parseInt(mgrsBandAdjust) - // variablesToUse.outputSamplingGridType = outputSamplingGridType.toUpperCase() - // } const utmVariables = { cycle: parseInt(cycle), @@ -129,14 +114,6 @@ export const getUserProducts = async () => { try { const userProductResponse = await graphQLClient.request(userProductsQuery).then(result => { const userProductsResult = (result as UserResponse).currentUser.products - // const userProductsGeneratedForm = userProductResponse.result.map(productResult => { - // const {cycle, pass, scene, rasterResolution, outputGranuleExtentFlag, outputSamplingGridType, utmZoneAdjust, timestamp, id: productId, status} = productResult - // // const generatedFormToReuturn: GeneratedProduct = - // // return generatedFormToReuturn - // }) - - // turn into GeneratedProduct - // const generatedProduct: GeneratedProduct = {} return {status: 'success', products: userProductsResult} as getUserProductsResponse }) return userProductResponse diff --git a/terraform/environments/ops.env b/terraform/environments/ops.env new file mode 100644 index 0000000..49238da --- /dev/null +++ b/terraform/environments/ops.env @@ -0,0 +1,6 @@ +export REGION=us-west-2 + +export REACT_APP_SWODLR_API_BASE_URI="https://swodlr.podaac.earthdatacloud.nasa.gov/api" +export REACT_APP_EDL_BASE_URI="https://swodlr.podaac.earthdatacloud.nasa.gov/api/edl/" +export REACT_APP_BASE_REDIRECT_URI="https://swodlr.podaac.earthdatacloud.nasa.gov/" +export REACT_APP_EDL_CLIENT_ID="n0oQvwh7W8Y1d7GVtNsFsQ"