From f2b7d723a62071b0b3c3e62401055e2dc58ede6c Mon Sep 17 00:00:00 2001 From: cqbanh Date: Tue, 12 Mar 2024 08:49:59 +0000 Subject: [PATCH 01/60] Deploying --- CHANGE | 1 + 1 file changed, 1 insertion(+) create mode 100644 CHANGE diff --git a/CHANGE b/CHANGE new file mode 100644 index 0000000..acca8be --- /dev/null +++ b/CHANGE @@ -0,0 +1 @@ +podaac/swodlr-ui deployment From 919fdaffda47ff166d35618f465ab6ae220101c4 Mon Sep 17 00:00:00 2001 From: Curtis Banh <30607061+cqbanh@users.noreply.github.com> Date: Tue, 12 Mar 2024 02:22:10 -0700 Subject: [PATCH 02/60] Update build.yml - OPS environment variable for release --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fb0de5b..74d700d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -113,6 +113,7 @@ jobs: THE_VERSION=${{ steps.package-version.outputs.current-version}} echo "software_version=${THE_VERSION//-*}" >> $GITHUB_ENV npm --no-git-tag-version version --allow-same-version ${THE_VERSION//-*} + echo "TARGET_ENV=OPS" >> $GITHUB_ENV - name: Set the target environment to ${{ env.TARGET_ENV }} id: set-env run: | @@ -296,4 +297,4 @@ jobs: payload: | { "message": "ERROR: ${{ github.repository }} [version ${{ needs.build.outputs.software_version }}] has encountered an error while trying to deploy to the ${{ needs.build.outputs.deploy_env }} environment" - } \ No newline at end of file + } From e1fcd62ef6bfdfd5c9a7757f88670d5730e8379c Mon Sep 17 00:00:00 2001 From: Curtis Banh <30607061+cqbanh@users.noreply.github.com> Date: Tue, 12 Mar 2024 02:49:34 -0700 Subject: [PATCH 03/60] Update build.yml - add OPS choice --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 74d700d..9cb8c99 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,6 +27,7 @@ on: options: - SIT - UAT + - OPS commit: type: string description: Custom commit hash From a52fcc3d8da4aa0a16c2e3e68e97ac5cffa2a9b8 Mon Sep 17 00:00:00 2001 From: cqbanh Date: Tue, 12 Mar 2024 09:51:37 +0000 Subject: [PATCH 04/60] /version 1.0.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3c2e8b6..a26521c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.0.0-rc.8", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.0.0-rc.8", + "version": "1.0.0", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index 09ff4eb..910daf8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.0.0-rc.8", + "version": "1.0.0", "private": true, "engines": { "node": ">=18.0.0" From 4aaac149b5b48cab8775666a18bd7f10e4672e5f Mon Sep 17 00:00:00 2001 From: Curtis Banh <30607061+cqbanh@users.noreply.github.com> Date: Tue, 12 Mar 2024 03:35:18 -0700 Subject: [PATCH 05/60] Update build.yml - check for staged changes --- .github/workflows/build.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9cb8c99..8405be0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -174,10 +174,14 @@ jobs: steps.rc.conclusion == 'success' || steps.release.conclusion == 'success' run: | - git config user.name "${GITHUB_ACTOR}" - git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" - git commit -am "/version ${{ env.software_version }}" - git push + if ! git diff --cached --exit-code; then + git config user.name "${GITHUB_ACTOR}" + git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" + git commit -am "/version ${{ env.software_version }}" + git push + else + echo "No staged changes" + fi - name: Push Tag if: | steps.alpha.conclusion == 'success' || From 59d35c8471711374f01c4c5d0301550c1a79d053 Mon Sep 17 00:00:00 2001 From: cqbanh Date: Thu, 14 Mar 2024 07:14:42 +0000 Subject: [PATCH 06/60] Deploying --- CHANGE | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGE b/CHANGE index acca8be..c04e256 100644 --- a/CHANGE +++ b/CHANGE @@ -1 +1,2 @@ podaac/swodlr-ui deployment +podaac/swodlr-ui deployment From 88787cb13f8d26785264c6a6251dadff72e1f5cf Mon Sep 17 00:00:00 2001 From: Curtis Banh <30607061+cqbanh@users.noreply.github.com> Date: Thu, 14 Mar 2024 00:41:21 -0700 Subject: [PATCH 07/60] Update build.yml - checks if tag exists --- .github/workflows/build.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8405be0..aae17f7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -190,7 +190,12 @@ jobs: run: | git config user.name "${GITHUB_ACTOR}" git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" - git tag -a "${{ env.software_version }}" -m "Version ${{ env.software_version }}" + + if git rev-parse "${{ env.software_version }}" >/dev/null 2>&1; then + git tag -a "${{ env.software_version }}" -m "Version ${{ env.software_version }}" --force + else + git tag -a "${{ env.software_version }}" -m "Version ${{ env.software_version }}" + fi git push origin "${{ env.software_version }}" - name: Create GH release if: | From 2d7344c629fa99009a507f68c279012c90fce2ca Mon Sep 17 00:00:00 2001 From: Curtis Banh <30607061+cqbanh@users.noreply.github.com> Date: Thu, 14 Mar 2024 00:48:52 -0700 Subject: [PATCH 08/60] Update build.yml - check for tag on remote --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aae17f7..32d3f68 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -191,7 +191,7 @@ jobs: git config user.name "${GITHUB_ACTOR}" git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" - if git rev-parse "${{ env.software_version }}" >/dev/null 2>&1; then + if git ls-remote --tags origin "${{ env.software_version }}" | grep -q "${{ env.software_version }}"; then git tag -a "${{ env.software_version }}" -m "Version ${{ env.software_version }}" --force else git tag -a "${{ env.software_version }}" -m "Version ${{ env.software_version }}" From e25efc82e50f4f9d62190153b51ec3b5a7103a04 Mon Sep 17 00:00:00 2001 From: Curtis Banh <30607061+cqbanh@users.noreply.github.com> Date: Thu, 14 Mar 2024 00:55:11 -0700 Subject: [PATCH 09/60] Update build.yml - replace tag if exists --- .github/workflows/build.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 32d3f68..5f47d10 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -191,11 +191,8 @@ jobs: git config user.name "${GITHUB_ACTOR}" git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" - if git ls-remote --tags origin "${{ env.software_version }}" | grep -q "${{ env.software_version }}"; then - git tag -a "${{ env.software_version }}" -m "Version ${{ env.software_version }}" --force - else - git tag -a "${{ env.software_version }}" -m "Version ${{ env.software_version }}" - fi + git tag -d "${{ env.software_version }}" || true + git tag -a "${{ env.software_version }}" -m "Version ${{ env.software_version }}" git push origin "${{ env.software_version }}" - name: Create GH release if: | From f7ec55661c0af1ca67bcd666e1a558250c4b129d Mon Sep 17 00:00:00 2001 From: Curtis Banh <30607061+cqbanh@users.noreply.github.com> Date: Thu, 14 Mar 2024 01:12:02 -0700 Subject: [PATCH 10/60] Update build.yml - trying force tag --- .github/workflows/build.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5f47d10..f5127b2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -190,10 +190,9 @@ jobs: run: | git config user.name "${GITHUB_ACTOR}" git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" - - git tag -d "${{ env.software_version }}" || true - git tag -a "${{ env.software_version }}" -m "Version ${{ env.software_version }}" - git push origin "${{ env.software_version }}" + + git tag -a "${{ env.software_version }}" -m "Version ${{ env.software_version }}" --force + git push origin "${{ env.software_version }}" --force - name: Create GH release if: | steps.alpha.conclusion == 'success' || From db3d98e5a73ba50988ad25ad75a7c0c34931591c Mon Sep 17 00:00:00 2001 From: Curtis Banh <30607061+cqbanh@users.noreply.github.com> Date: Thu, 14 Mar 2024 01:21:17 -0700 Subject: [PATCH 11/60] Update build.yml - allow release update --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f5127b2..aba80d1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -204,6 +204,7 @@ jobs: name: ${{ env.software_version }} prerelease: ${{ steps.alpha.conclusion == 'success' || steps.rc.conclusion == 'success'}} tag: ${{ env.software_version }} + allowUpdates: true - name: Set github SHA for deployment id: update-sha run: | From b9e4f82260ead397d0fc847ba68a1bc0b0501d1f Mon Sep 17 00:00:00 2001 From: cqbanh Date: Thu, 14 Mar 2024 08:47:20 +0000 Subject: [PATCH 12/60] Deploying --- CHANGE | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGE b/CHANGE index acca8be..c04e256 100644 --- a/CHANGE +++ b/CHANGE @@ -1 +1,2 @@ podaac/swodlr-ui deployment +podaac/swodlr-ui deployment From 7faf44980938d29d1f6ba3b366aa0c5bde9c2fc8 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Mon, 25 Mar 2024 14:57:34 -0700 Subject: [PATCH 13/60] Issues/swodlr UI final fixes - bug fixed before version 1.0 release (#91) * issues/swodlr-ui-final-fixes: fixed spatial search speed and tutorial back fix * issues/swodlr-ui-final-fixes: fixed spinner, 10 limit, tutorial, map on reload * issues/swodlr-ui-final-fixes: fixed delete bug and added success alert for generation * issues/swodlr-ui-final-fixes: made alert message for search area too large * issues/swodlr-ui-final-fixes: changed search area too large message * issues/swodlr-ui-final-fixes: start search polygon url param * issues/swodlr-ui-final-fixed: removed skip from tutorial, added copy/download tooltips product url * issues/swodlr-ui-final-fixes: added tutorial close confirmation modal * issues/swodlr-ui-final-fixes: added cmr SWOT collection permissions check * issues/swodlr-ui-final-fixes: made cmr permissions alert conditional * issues/swodlr-ui-final-fixes: fixed tutorial back error and no data history tutorial error --------- Co-authored-by: jbyrne --- src/components/about/About.tsx | 34 ++- src/components/app/App.tsx | 41 ++-- src/components/app/appSlice.ts | 13 +- .../edl/AuthorizationCodeHandler.tsx | 40 ++++ .../history/GeneratedProductHistory.tsx | 67 ++++-- src/components/map/WorldMap.tsx | 86 ++++++-- src/components/navbar/PodaacFooter.tsx | 17 +- .../sidebar/CustomizeProductView.tsx | 3 + .../sidebar/CustomizeProductsSidebar.tsx | 3 +- .../sidebar/DeleteGranulesModal.tsx | 7 +- .../sidebar/GenerateProductsModal.tsx | 10 +- .../GranuleSelectionAndConfigurationView.tsx | 18 +- .../sidebar/GranuleSelectionView.tsx | 16 +- src/components/sidebar/GranuleTableAlerts.tsx | 5 +- src/components/sidebar/GranulesTable.tsx | 199 +++++++++++------- .../sidebar/SpatialSearchOptions.tsx | 12 -- src/components/sidebar/actions/modalSlice.ts | 16 +- .../sidebar/actions/productSlice.ts | 10 +- .../InteractiveTutorialModalClose.tsx | 44 ++++ src/components/tutorial/tutorialConstants.ts | 2 +- src/components/welcome/Welcome.tsx | 2 +- src/constants/rasterParameterConstants.ts | 21 +- src/types/constantTypes.ts | 2 +- 23 files changed, 497 insertions(+), 171 deletions(-) create mode 100644 src/components/tutorial/InteractiveTutorialModalClose.tsx diff --git a/src/components/about/About.tsx b/src/components/about/About.tsx index e08c0a2..83c9a26 100644 --- a/src/components/about/About.tsx +++ b/src/components/about/About.tsx @@ -4,9 +4,19 @@ import CompareImage from '../../assets/comparing-images.png' import YukonImage from '../../assets/SWOT-YUKON.jpeg' import LatLongUTM from '../../assets/lat-lon-vs-utm.png' import UpSWOTResolution from '../../assets/swot-go-up-resolution.jpg' +import packageJson from '../../../package.json' +import { useEffect, useState } from "react"; const About = () => { - + const [backendVersion, setBackendVersion] = useState('') + useEffect(() => { + const fetchData = async () => { + setBackendVersion(await fetch('https://swodlr.podaac.sit.earthdatacloud.nasa.gov/api/about').then((version) => version.json()).then(response => response.version)) + } + fetchData() + .catch(console.error); + }, []); + return (

About: SWOT On-Demand Level-2 Raster Generator

@@ -18,7 +28,7 @@ const About = () => { SWODLR is an on-demand raster generation tool that generates customized Surface Water and Ocean Topography (SWOT) Level 2 raster products. SWOT standard products are released in geographically fixed tiles at 100m and 250m resolutions in a Universal Transverse Mercator (UTM) projection grid. SWODLR allows users to generate the same products at different resolutions in either the UTM or geodetic coordinate system (lat/lon). SWODLR also gives an option to change the output granule extent from a nonoverlapping square 128 km x 128 km to an overlapping rectangle 256 km x 128 km to assist with observing areas of interest near the along-track edges of the original square extent.
- Like the standard product, the on-demand product contains rasterized water surface elevation and inundation-extents. This is derived through resampling the upstream pixel cloud (L2_HR_PIXC) and pixel vector (L2_HR_PIXCVEC) datasets onto a uniform grid. A uniform grid is superimposed onto the pixel cloud from the source products, and all pixel-cloud samples within each grid cell are aggregated to produce a single value per raster cell. SWODLR uses the original algorithm that standard SWOT products use to generate products but at a different resolution; it does not just re-grid the standard products. + Like the standard product, the on-demand product contains rasterized water surface elevation and inundation-extents. This is derived through resampling the upstream pixel cloud (L2_HR_PIXC) and pixel vector (L2_HR_PIXCVEC) datasets onto a uniform grid. A uniform grid is superimposed onto the pixel cloud from the source products, and all pixel-cloud samples within each grid cell are aggregated to produce a single value per raster cell. SWODLR uses the original algorithm that standard SWOT products use to generate products but at a different resolution; it does not just re-grid the standard products.
@@ -70,8 +80,6 @@ const About = () => { - {/*

FAQ

*/} -

Definitions

@@ -131,10 +139,24 @@ const About = () => {
-

Version History

+

Current Version

-
Version 1 (9/05/2023)
+ +
+ SWODLR UI: + {packageJson.version} +
+
+
{`(Release Notes)`}
+
+ + +
+ SWODLR API: + {backendVersion} +
+
diff --git a/src/components/app/App.tsx b/src/components/app/App.tsx index eaf6719..d8984af 100644 --- a/src/components/app/App.tsx +++ b/src/components/app/App.tsx @@ -14,10 +14,12 @@ import { Session } from '../../authentication/session'; import { getCurrentUser, setStartTutorial } from './appSlice'; import { useEffect, useState } from 'react'; import GranuleSelectionAndConfigurationView from '../sidebar/GranuleSelectionAndConfigurationView'; -import Joyride from 'react-joyride'; +import Joyride, { ACTIONS, EVENTS } from 'react-joyride'; import { deleteProduct } from '../sidebar/actions/productSlice'; import { tutorialSteps } from '../tutorial/tutorialConstants'; import InteractiveTutorialModal from '../tutorial/InteractiveTutorialModal'; +import { setShowCloseTutorialTrue, setSkipTutorialTrue } from '../sidebar/actions/modalSlice'; +import InteractiveTutorialModalClose from '../tutorial/InteractiveTutorialModalClose'; const App = () => { const dispatch = useAppDispatch() @@ -33,33 +35,46 @@ const App = () => { const [joyride, setState] = useState({ run: startTutorial, - steps: tutorialSteps + steps: tutorialSteps, + stepIndex: 0 }) + useEffect(() => { - setState({...joyride, run: startTutorial }) + setState({...joyride, run: startTutorial, stepIndex: 0}) + // eslint-disable-next-line react-hooks/exhaustive-deps }, [startTutorial]); const handleJoyrideCallback = (data: { action: any; index: any; status: any; type: any; step: any; lifecycle: any; }) => { - const { action, step, type, lifecycle } = data; + const { action, step, type, lifecycle, index } = data; const stepTarget = step.target - if (stepTarget === '#configure-options-breadcrumb' && action === 'update') { + + if ([EVENTS.STEP_AFTER, EVENTS.TARGET_NOT_FOUND].includes(type)) { + // Update state to advance the tour + setState({...joyride, stepIndex: index + (action === ACTIONS.PREV ? -1 : 1) }); + } + + if (action === 'close') { + dispatch(setShowCloseTutorialTrue()) + } else if (stepTarget === '#configure-options-breadcrumb' && action === 'update') { navigate(`/customizeProduct/configureOptions${search}`) } else if (stepTarget === '#configure-options-breadcrumb' && action === 'prev' && lifecycle === 'complete') { navigate(`/customizeProduct/selectScenes${search}`) - } else if (stepTarget === '#my-data-page' && action === 'prev') { + } + else if (stepTarget === '#my-data-page' && action === 'prev' && lifecycle === 'complete') { navigate(`/customizeProduct/configureOptions${search}`) - } else if (stepTarget === '#added-scenes' && action === 'update') { - navigate(`/customizeProduct/selectScenes?cyclePassScene=1_413_120&showUTMAdvancedOptions=true`) + } + else if (stepTarget === '#added-scenes' && action === 'update') { + navigate(`/customizeProduct/selectScenes?cyclePassScene=9_515_130&showUTMAdvancedOptions=true`) } else if (stepTarget === '#customization-tab' && action === 'start') { navigate('/customizeProduct/selectScenes') - } else if ((stepTarget === '#generate-products-button' && action === 'close' && lifecycle === 'complete') || (stepTarget === '#my-data-page' && action === 'next')) { + } else if (action === 'next' && stepTarget === '#my-data-page') { navigate(`/generatedProductHistory${search}`) } else if (type === 'tour:end') { - dispatch(deleteProduct(addedProducts.map(product => product.granuleId))) + dispatch(setSkipTutorialTrue()) dispatch(setStartTutorial(false)) + dispatch(deleteProduct(addedProducts.map(product => product.granuleId))) 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(() => { @@ -103,9 +118,8 @@ const App = () => { callback={(data) => handleJoyrideCallback(data)} run={joyride.run} steps={joyride.steps} + stepIndex={joyride.stepIndex} showProgress - showSkipButton - hideCloseButton continuous scrollToFirstStep /> @@ -119,6 +133,7 @@ const App = () => { , true)}/> +
); } diff --git a/src/components/app/appSlice.ts b/src/components/app/appSlice.ts index 2194e64..624b694 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: true } 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 5ada8e8..4d9be22 100644 --- a/src/components/edl/AuthorizationCodeHandler.tsx +++ b/src/components/edl/AuthorizationCodeHandler.tsx @@ -5,6 +5,46 @@ import { exchangeAuthenticationCode } from "../../authentication/edl"; import { OAuthTokenExchangeFailed } from "../../authentication/exception"; import { Session } from "../../authentication/session"; +import { spatialSearchCollectionConceptId, spatialSearchResultLimit } from "../../constants/rasterParameterConstants"; + +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(); const [searchParams] = useSearchParams(); diff --git a/src/components/history/GeneratedProductHistory.tsx b/src/components/history/GeneratedProductHistory.tsx index 8dfbee0..474a0b3 100644 --- a/src/components/history/GeneratedProductHistory.tsx +++ b/src/components/history/GeneratedProductHistory.tsx @@ -1,4 +1,4 @@ -import { Alert, Col, OverlayTrigger, Row, Table, Tooltip, Button } from "react-bootstrap"; +import { Alert, Col, OverlayTrigger, Row, Table, Tooltip, Button, Spinner } from "react-bootstrap"; import { useAppSelector } from "../../redux/hooks"; import { getUserProductsResponse, Product } from "../../types/graphqlTypes"; import { useEffect, useState } from "react"; @@ -12,10 +12,14 @@ const GeneratedProductHistory = () => { const { search } = useLocation(); const navigate = useNavigate() const [userProducts, setUserProducts] = useState([]) + const [waitingForProductsToLoad, setWaitingForProductsToLoad] = useState(true) useEffect(() => { const fetchData = async () => { - const userProductsResponse: getUserProductsResponse = await getUserProducts() + const userProductsResponse: getUserProductsResponse = await getUserProducts().then((response) => { + setWaitingForProductsToLoad(false) + return response + }) if (userProductsResponse.status === 'success') setUserProducts(userProductsResponse.products as Product[]) } fetchData() @@ -42,6 +46,32 @@ const GeneratedProductHistory = () => { ) + + const renderCopyDownloadButton = (downloadUrlString: string) => ( + + Copy + + } + > + + + ) + + const renderDownloadButton = (downloadUrlString: string) => ( + + Download + + } + > + + + ) const renderColTitle = (labelEntry: string[], index: number) => { let infoIcon = infoIconsToRender.includes(labelEntry[0]) ? renderInfoIcon(labelEntry[0]) : null @@ -78,10 +108,10 @@ const GeneratedProductHistory = () => { if (entry[0] === 'downloadUrl' && entry[1] !== 'N/A') { const downloadUrlString = granules[0].uri cellContents = - + {entry[1]} - - + {(renderCopyDownloadButton(downloadUrlString))} + {renderDownloadButton(downloadUrlString)} } else { cellContents = entry[1] @@ -103,20 +133,33 @@ const GeneratedProductHistory = () => { return navigate(`/generatedProductHistory${search}`)} style={{cursor: 'pointer'}}>{alertMessage} } + const waitingForProductsToLoadSpinner = () => { + return ( +
+
Loading Data Table...
+ + Loading... + +
+ ) + } + const renderProductHistoryViews = () => { return ( - -

Generated Products Data

- {renderHistoryTable()} - {userProducts.length === 0 ? {productHistoryAlert()} : null} + + {renderHistoryTable()} + {userProducts.length === 0 ? {productHistoryAlert()} : null} ) } return ( - - {renderProductHistoryViews()} - + <> +

Generated Products Data

+ + {waitingForProductsToLoad ? waitingForProductsToLoadSpinner() : renderProductHistoryViews()} + + ); } diff --git a/src/components/map/WorldMap.tsx b/src/components/map/WorldMap.tsx index 0ae56e4..75b413f 100644 --- a/src/components/map/WorldMap.tsx +++ b/src/components/map/WorldMap.tsx @@ -12,7 +12,8 @@ import booleanClockwise from '@turf/boolean-clockwise'; import { afterCPSL, afterCPSR, beforeCPS, spatialSearchCollectionConceptId, spatialSearchResultLimit } from '../../constants/rasterParameterConstants'; import { addSpatialSearchResults, setMapFocus, setWaitingForSpatialSearch } from '../sidebar/actions/productSlice'; import { SpatialSearchResult } from '../../types/constantTypes'; -import { useLocation } from 'react-router-dom'; +import { useLocation, useSearchParams } from 'react-router-dom'; +import { useEffect } from 'react'; let DefaultIcon = L.icon({ iconUrl: icon, @@ -20,29 +21,63 @@ let DefaultIcon = L.icon({ }); L.Marker.prototype.options.icon = DefaultIcon; -function UpdateMapCenter() { +const UpdateMapCenter = () => { const dispatch = useAppDispatch() const mapFocus = useAppSelector((state) => state.product.mapFocus) + // search parameters + const [searchParams, setSearchParams] = useSearchParams() + + // put the current center and zoom into the url parameters + const handleMapFocus = (center: number[], zoom: number) => { + const currentSearchParams = Object.fromEntries(searchParams.entries()) + currentSearchParams.center = `${center[0]},${center[1]}` + currentSearchParams.zoom = String(zoom) + setSearchParams(currentSearchParams) + dispatch(setMapFocus({center, zoom})) + } 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})) + if ((mapFocus.center[0] !== center[0] && mapFocus.center[1] !== center[1]) || mapFocus.zoom !== zoom) handleMapFocus(center, zoom) }) return null } +const ChangeView = () => { + const mapFocus = useAppSelector((state) => state.product.mapFocus) + const map = useMap() + map.setView(mapFocus.center as LatLngExpression, mapFocus.zoom) + return null +} + const WorldMap = () => { const addedProducts = useAppSelector((state) => state.product.addedProducts) const mapFocus = useAppSelector((state) => state.product.mapFocus) + const userHasCorrectEdlPermissions = useAppSelector((state) => state.app.userHasCorrectEdlPermissions) const dispatch = useAppDispatch() const footprintStyleOptions = { color: 'limegreen' } + // search parameters + const [searchParams, setSearchParams] = useSearchParams() - const ChangeView = () => { - const map = useMap() - map.setView(mapFocus.center as LatLngExpression, mapFocus.zoom) - return null - } + useEffect(() => { + // if center and zoom are in url params, set the current center to them + const center = searchParams.get('center') + const zoom = searchParams.get('zoom') + if (center && zoom) { + const centerParamSplit = center.split(',') + const centerToUse: number[] = [parseFloat(centerParamSplit[0]), parseFloat(centerParamSplit[1])] + const zoomToUse = parseInt(zoom) + if (centerToUse !== mapFocus.center || zoomToUse !== mapFocus.zoom) { + dispatch(setMapFocus({center: centerToUse, zoom: zoomToUse})) + } + } + + // TODO: implement search polygon search param + // const searchPolygon = searchParams.get('searchPolygon') + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const getScenesWithinCoordinates = async (coordinatesToSearch: {lat: number, lng: number}[][]) => { try { @@ -55,7 +90,6 @@ const WorldMap = () => { if (authToken === null) { throw new Error('Failed to get authentication token'); } - dispatch(setWaitingForSpatialSearch(true)) const polygonUrlString = coordinatesToSearch.map((polygon) => { @@ -84,9 +118,12 @@ const WorldMap = () => { headers: { Authorization: `Bearer ${authToken}` } - }).then(response => response.text()).then(data => { + }).then(async data => { + const responseText = await data.text() + // TODO: make subsequent calls to get granules in spatial search area till everything is found. + // current issue is that 1000 (2000 total divided by 2) is limited by the cmr api. const parser = new DOMParser(); - const xml = parser.parseFromString(data, "application/xml"); + const xml = parser.parseFromString(responseText, "application/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 => { @@ -98,9 +135,7 @@ const WorldMap = () => { return references }) dispatch(addSpatialSearchResults(spatialSearchResponse as SpatialSearchResult[])) - dispatch(setWaitingForSpatialSearch(false)) } catch (err) { - dispatch(setWaitingForSpatialSearch(false)) if (err instanceof Error) { return err } else { @@ -110,9 +145,17 @@ const WorldMap = () => { } const onCreate = async (createEvent: any) => { - await getScenesWithinCoordinates([createEvent.layer.getLatLngs()[0]]) - // set the new map focus location to what it was when polygon created so it will stay the same after map reload - dispatch(setMapFocus({center: [createEvent.layer._renderer._center.lat, createEvent.layer._renderer._center.lng], zoom: createEvent.target._zoom})) + const searchPolygonLatLngs = createEvent.layer.getLatLngs()[0] + + // TODO: implement search polygon search param + // const currentSearchParams = Object.fromEntries(searchParams.entries()) + // const searchPolygonLatLngsString = searchPolygonLatLngs.map((latLngObject: {lat: number, lng: number}) => `${latLngObject.lat},${latLngObject.lng}`).join('_') + // currentSearchParams.searchPolygon = searchPolygonLatLngsString + // setSearchParams(currentSearchParams) + + await getScenesWithinCoordinates([searchPolygonLatLngs]) + // set the new map focus location to what it was when polygon created so it will stay the same after map reload + dispatch(setMapFocus({center: [createEvent.layer._renderer._center.lat, createEvent.layer._renderer._center.lng], zoom: createEvent.target._zoom})) } const onEdit = async (editEvent: any) => { @@ -128,7 +171,7 @@ const WorldMap = () => { id='spatial-search-map' zoom={7} scrollWheelZoom={true} zoomControl={false} > - {useLocation().pathname.includes('selectScenes') ? ( + {(useLocation().pathname.includes('selectScenes') && userHasCorrectEdlPermissions) ? ( { url='https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}' attribution='Esri, Maxar, Earthstar Geographics, and the GIS User Community' maxZoom = {18} + noWrap + bounds={ + [ + [-89.9999, -179.9999], + [89.9999, 179.9999] + ] + } /> {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/navbar/PodaacFooter.tsx b/src/components/navbar/PodaacFooter.tsx index 3a4725e..5ae3e14 100644 --- a/src/components/navbar/PodaacFooter.tsx +++ b/src/components/navbar/PodaacFooter.tsx @@ -2,9 +2,11 @@ import Navbar from 'react-bootstrap/Navbar'; import { useAppSelector } from '../../redux/hooks' import { Col, Row } from 'react-bootstrap'; import { useLocation, useNavigate } from 'react-router-dom'; +import packageJson from '../../../package.json' const PodaacFooter = () => { const colorModeClass = useAppSelector((state) => state.navbar.colorModeClass) + const userAuthenticated = useAppSelector((state) => state.app.userAuthenticated) const navigate = useNavigate(); const { search } = useLocation(); @@ -12,7 +14,7 @@ const PodaacFooter = () => { - Version 1.0 Pre-Alpha of SWOT On-Demand Level-2 Raster Generator (SWODLR) + {`Version ${packageJson.version} Beta of SWOT On-Demand Level-2 Raster Generator (SWODLR)`} @@ -21,11 +23,14 @@ const PodaacFooter = () => { Privacy - - navigate(`/about${search}`)}> - About SWODLR - - + { userAuthenticated ? + ( + navigate(`/about${search}`)}> + About SWODLR + + ) + : null + } window.open('mailto:podaac@podaac.jpl.nasa.gov')}> Contact diff --git a/src/components/sidebar/CustomizeProductView.tsx b/src/components/sidebar/CustomizeProductView.tsx index bc47460..3eba245 100644 --- a/src/components/sidebar/CustomizeProductView.tsx +++ b/src/components/sidebar/CustomizeProductView.tsx @@ -1,6 +1,7 @@ import GranuleTable from './GranulesTable'; import ProductCustomization from './ProductCustomization'; import GenerateProducts from './GenerateProducts'; +import GranuleTableAlerts from './GranuleTableAlerts'; const GranuleSelectionView = () => { return ( @@ -9,6 +10,8 @@ const GranuleSelectionView = () => {
+ + ); } diff --git a/src/components/sidebar/CustomizeProductsSidebar.tsx b/src/components/sidebar/CustomizeProductsSidebar.tsx index ee0eff9..c88379e 100644 --- a/src/components/sidebar/CustomizeProductsSidebar.tsx +++ b/src/components/sidebar/CustomizeProductsSidebar.tsx @@ -47,6 +47,7 @@ const CustomizeProductsSidebar = (props: CustomizeProductSidebarProps) => { setLocalSidebarWidth(sidebarWidthNumber) setSidebarWidth(sidebarWidthNumber) + // eslint-disable-next-line react-hooks/exhaustive-deps }, [resizeEndLocation]) return ( @@ -57,8 +58,6 @@ const CustomizeProductsSidebar = (props: CustomizeProductSidebarProps) => { {renderSidebarContents()} - - {/* TODO: uncomment when granule footprints are being retrieved to display on map */}
handleResizeClickDown(event)}> handleResizeClickDown(event)}/>
diff --git a/src/components/sidebar/DeleteGranulesModal.tsx b/src/components/sidebar/DeleteGranulesModal.tsx index 2ed6d47..1f1b10c 100644 --- a/src/components/sidebar/DeleteGranulesModal.tsx +++ b/src/components/sidebar/DeleteGranulesModal.tsx @@ -15,7 +15,12 @@ const GenerateProductsModal = () => { const removeCPSFromUrl = (cpsCombosToRemove: string[]) => { const cyclePassSceneParameters = searchParams.get('cyclePassScene')?.split('-') if (cyclePassSceneParameters) { - const cyclePassSceneParametersToKeep = cyclePassSceneParameters.filter(cpsCombo => !cpsCombosToRemove.includes(cpsCombo)).join('-') + const cyclePassSceneParametersToKeep = cyclePassSceneParameters.filter(cpsCombo => { + const urlCPSSplit = cpsCombo.split('_') + const reconstructedUrlCPS = `${urlCPSSplit[0]}_${urlCPSSplit[1]}_${urlCPSSplit[2]}` + const keepCPSCombo = !cpsCombosToRemove.includes(reconstructedUrlCPS) + return keepCPSCombo + }).join('-') const currentUrlParameters = Object.fromEntries(searchParams.entries()) if (cyclePassSceneParametersToKeep.length === 0) { const {cyclePassScene, ...restOfCurrentUrlParameters} = currentUrlParameters diff --git a/src/components/sidebar/GenerateProductsModal.tsx b/src/components/sidebar/GenerateProductsModal.tsx index de92f11..aa83945 100644 --- a/src/components/sidebar/GenerateProductsModal.tsx +++ b/src/components/sidebar/GenerateProductsModal.tsx @@ -2,18 +2,26 @@ import Button from 'react-bootstrap/Button'; import Modal from 'react-bootstrap/Modal'; import { useAppSelector, useAppDispatch } from '../../redux/hooks' import { setShowGenerateProductModalFalse } from './actions/modalSlice' -import { addGeneratedProducts } from './actions/productSlice' +import { addGeneratedProducts, addGranuleTableAlerts } from './actions/productSlice' import { Row } from 'react-bootstrap'; +import { granuleAlertMessageConstant } from '../../constants/rasterParameterConstants'; +import { alertMessageInput } from '../../types/constantTypes'; const GenerateProductsModal = () => { const showGenerateProductModal = useAppSelector((state) => state.modal.showGenerateProductModal) const addedGranules = useAppSelector((state) => state.product.addedProducts) const dispatch = useAppDispatch() + const setSaveGranulesAlert = (alert: alertMessageInput, additionalParameters?: any[]) => { + const {message, variant} = granuleAlertMessageConstant[alert] + dispatch(addGranuleTableAlerts({type: alert, message, variant, tableType: 'productCustomization' })) + } + const handleGenerate = () => { // unselect select-all box dispatch(addGeneratedProducts(addedGranules.map(granuleObj => granuleObj.granuleId))) dispatch(setShowGenerateProductModalFalse()) + setSaveGranulesAlert('successfullyGenerated') } return ( diff --git a/src/components/sidebar/GranuleSelectionAndConfigurationView.tsx b/src/components/sidebar/GranuleSelectionAndConfigurationView.tsx index e8b766b..17bac93 100644 --- a/src/components/sidebar/GranuleSelectionAndConfigurationView.tsx +++ b/src/components/sidebar/GranuleSelectionAndConfigurationView.tsx @@ -4,18 +4,32 @@ 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 userAuthenticated = useAppSelector((state) => state.app.userAuthenticated) const {mode} = props useEffect(() => { - if (!skipTutorial) { + const fetchData = async () => { + const userHasCorrectEdlPermissions = await checkUseHasCorrectEdlPermissions() + dispatch(setUserHasCorrectEdlPermissions(userHasCorrectEdlPermissions)) + } + + // call the function + fetchData() + }, []) + + useEffect(() => { + if (!skipTutorial && userAuthenticated) { dispatch(setShowTutorialModalTrue()) dispatch(setSkipTutorialTrue()) } - }, []); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [userAuthenticated]) return ( <> diff --git a/src/components/sidebar/GranuleSelectionView.tsx b/src/components/sidebar/GranuleSelectionView.tsx index c88d913..bd1f46d 100644 --- a/src/components/sidebar/GranuleSelectionView.tsx +++ b/src/components/sidebar/GranuleSelectionView.tsx @@ -1,13 +1,27 @@ +import { Alert, Col, Row } from 'react-bootstrap'; import GranuleTable from './GranulesTable'; import GranuleTableAlerts from './GranuleTableAlerts'; import SpatialSearchOptions from './SpatialSearchOptions'; +import { useAppSelector } from '../../redux/hooks'; const GranuleSelectionView = () => { + const userHasCorrectEdlPermissions = useAppSelector((state) => state.app.userHasCorrectEdlPermissions) return (
- + + { + userHasCorrectEdlPermissions ? + null : +
+ + + The SWOT dataset is not public yet. Until then, some functionality of this site will be limited. You are not yet able to add scenes, configure scenes, or generate products. + + +
+ }
); } diff --git a/src/components/sidebar/GranuleTableAlerts.tsx b/src/components/sidebar/GranuleTableAlerts.tsx index f832aa7..48bfe99 100644 --- a/src/components/sidebar/GranuleTableAlerts.tsx +++ b/src/components/sidebar/GranuleTableAlerts.tsx @@ -1,13 +1,14 @@ import { useAppSelector } from '../../redux/hooks' import { Alert, Col, Row } from 'react-bootstrap'; import DeleteGranulesModal from './DeleteGranulesModal'; +import { TableTypes } from '../../types/constantTypes'; -const GranuleTableAlerts = () => { +const GranuleTableAlerts: React.FC<{tableType: TableTypes}> = ({tableType}) => { const granuleTableAlerts = useAppSelector((state) => state.product.granuleTableAlerts) return (
- {granuleTableAlerts.map((alertObject, index) => ( + {granuleTableAlerts.filter(item => item.tableType === tableType).map((alertObject, index) => ( {alertObject.message} diff --git a/src/components/sidebar/GranulesTable.tsx b/src/components/sidebar/GranulesTable.tsx index 4bb8342..3e42b76 100644 --- a/src/components/sidebar/GranulesTable.tsx +++ b/src/components/sidebar/GranulesTable.tsx @@ -6,7 +6,7 @@ import { granuleAlertMessageConstant, granuleSelectionLabels, productCustomizati 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, handleSaveResult, validScene } from '../../types/constantTypes'; -import { addProduct, setSelectedGranules, setGranuleFocus, addGranuleTableAlerts, editProduct, addSpatialSearchResults, setWaitingForFootprintSearch, clearGranuleTableAlerts } from './actions/productSlice'; +import { addProduct, setSelectedGranules, setGranuleFocus, addGranuleTableAlerts, editProduct, addSpatialSearchResults, clearGranuleTableAlerts, setWaitingForSpatialSearch } from './actions/productSlice'; import { setShowDeleteProductModalTrue } from './actions/modalSlice'; import DeleteGranulesModal from './DeleteGranulesModal'; import { graphQLClient } from '../../user/userData'; @@ -23,9 +23,10 @@ const GranuleTable = (props: GranuleTableProps) => { const generateProductParameters = useAppSelector((state) => state.product.generateProductParameters) const showUTMAdvancedOptions = useAppSelector((state) => state.product.showUTMAdvancedOptions) const waitingForSpatialSearch = useAppSelector((state) => state.product.waitingForSpatialSearch) - const waitingForFootprintSearch = useAppSelector((state) => state.product.waitingForFootprintSearch) const spatialSearchStartDate = useAppSelector((state) => state.product.spatialSearchStartDate) const spatialSearchEndDate = useAppSelector((state) => state.product.spatialSearchEndDate) + const startTutorial = useAppSelector((state) => state.app.startTutorial) + const userHasCorrectEdlPermissions = useAppSelector((state) => state.app.userHasCorrectEdlPermissions) const dispatch = useAppDispatch() @@ -37,7 +38,6 @@ const GranuleTable = (props: GranuleTableProps) => { // set the default url state parameters useEffect(() => { - dispatch(clearGranuleTableAlerts()) // if any cycle scene and pass parameters in url, add them to table const cyclePassSceneParameters = searchParams.get('cyclePassScene') if (cyclePassSceneParameters) { @@ -47,33 +47,93 @@ const GranuleTable = (props: GranuleTableProps) => { handleSave('urlParameter', sceneParamArray.length, index, splitSceneParams[0], splitSceneParams[1], splitSceneParams[2]) }) } - }, [tableType === 'granuleSelection' ? null : addedProducts]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [tableType === 'granuleSelection' ? null : addedProducts, startTutorial ? searchParams : null]) + const validateSceneAvailability = async (cycleToUse: number, passToUse: number, sceneToUse: number[], cpsList?: {cycle: string, pass: string, scene: string}[]): Promise => { + try { + // build graphql availableScene query with all cycle/pass/scene combos requested + let queryAliasString = `` + // if there is a list of cycle pass and scenes go through them (spatial search) and if not, use first 3 function params (manual search) + if (cpsList) { + for(const specificCPS of cpsList) { + const {cycle, pass, scene} = specificCPS + const comboId = `${cycle}_${pass}_${scene}` + queryAliasString += ` s_${comboId}: availableScene(cycle: ${cycle}, pass: ${pass}, scene: ${scene}) ` + } + } else { + for(const specificScene of sceneToUse) { + const comboId = `${cycleToUse}_${passToUse}_${specificScene}` + queryAliasString += ` s_${comboId}: availableScene(cycle: ${cycleToUse}, pass: ${passToUse}, scene: ${specificScene}) ` + } + } + const queryAliasObject = `{${queryAliasString}}` + const res: {availableScene: boolean} = await graphQLClient.request(queryAliasObject).then(response => { + const responseToReturn = Object.fromEntries(Object.entries(response as {availableScene: boolean}).map(responseObj => [responseObj[0].replace('s_', ''), responseObj[1]])) + return responseToReturn as {availableScene: boolean} + }) + return res + + } catch (err) { + console.log (err) + return {} + } + } + + // Spatial search use effect useEffect(() => { dispatch(clearGranuleTableAlerts()) if (spatialSearchResults.length > 0) { let scenesFoundArray: string[] = [] let addedScenes: string[] = [] + const fetchData = async () => { - for(let i=0; i { - if(result.savedScenes) { - addedScenes.push(...(result.savedScenes).map(productObject => productObject.granuleId)) + if (spatialSearchResults.length < 1000) { + // check validity before saving + const validationResult = await validateSceneAvailability(0,0,[0],spatialSearchResults).then(result => Object.entries(result).filter(resultEntry => resultEntry[1]).map(valuePair => { + const cpsSplit = valuePair[0].split('_') + return {cycle: cpsSplit[0], pass: cpsSplit[1], scene: cpsSplit[2]} + })) + + if (validationResult.length > 0) { + for(let i=0; i result === 'found something').length) >= granuleTableLimit) { + // don't let more than 10 be added + scenesFoundArray.push('hit granule limit') + } else { + await handleSave('spatialSearch', validationResult.length, i, validationResult[i].cycle, validationResult[i].pass, validationResult[i].scene).then(result => { + if(result.savedScenes) { + addedScenes.push(...(result.savedScenes).map(productObject => productObject.granuleId)) + } + scenesFoundArray.push(result.result) + }) + } + } - scenesFoundArray.push(result.result) - }) + if(addedScenes.length > 0) { + // add parameters + addSearchParamToCurrentUrlState({'cyclePassScene': addedScenes.join('-')}) + } + } else { + scenesFoundArray.push('noScenesFound') + } + } else { + // If too many spatial search results, the search doesn't work because there too many granules and a limit was reached. + // In this scenario, make an alert that indicates that the search area was too large. + // TODO: remove this alert when there is a fix implemented for cmr spatial search limit. + // The valid granules are sometimes not a part of the first 1000 results which is the bug here. + scenesFoundArray.push('spatialSearchAreaTooLarge') } - // add parameters - addSearchParamToCurrentUrlState({'cyclePassScene': addedScenes.join('-')}) + dispatch(setWaitingForSpatialSearch(false)) return scenesFoundArray } // call the function fetchData() .then((noScenesFoundResults) => { - if(noScenesFoundResults.includes('noScenesFound') && !noScenesFoundResults.includes('found something')){ - setSaveGranulesAlert('noScenesFound') - } + if(noScenesFoundResults.includes('noScenesFound') && !noScenesFoundResults.includes('found something')) setSaveGranulesAlert('noScenesFound') + if(noScenesFoundResults.includes('hit granule limit')) setSaveGranulesAlert('granuleLimit') + if(noScenesFoundResults.includes('spatialSearchAreaTooLarge')) setSaveGranulesAlert('spatialSearchAreaTooLarge') }) // make sure to catch any error .catch(console.error); @@ -81,6 +141,7 @@ const GranuleTable = (props: GranuleTableProps) => { // clear spatial results out of redux after use if(spatialSearchResults.length !== 0) dispatch(addSpatialSearchResults([] as SpatialSearchResult[])) } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [spatialSearchResults]) const addSearchParamToCurrentUrlState = (newPairsObject: object, remove?: string) => { @@ -136,26 +197,7 @@ const GranuleTable = (props: GranuleTableProps) => { const [scene, setScene] = useState(''); const allAddedGranules = addedProducts.map(parameterObject => parameterObject.granuleId) const [waitingForScenesToBeAdded, setWaitingForScenesToBeAdded] = useState(false) - -const validateSceneAvailability = async (cycleToUse: number, passToUse: number, sceneToUse: number[]): Promise => { - try { - // build graphql availableScene query with all cycle/pass/scene combos requested - let queryAliasString = `` - for(const specificScene of sceneToUse) { - const comboId = `${cycleToUse}_${passToUse}_${specificScene}` - queryAliasString += ` s_${comboId}: availableScene(cycle: ${cycleToUse}, pass: ${passToUse}, scene: ${specificScene}) ` - } - const queryAliasObject = `{${queryAliasString}}` - const res: {availableScene: boolean} = await graphQLClient.request(queryAliasObject, {cycle: cycleToUse, pass: passToUse, scene: sceneToUse[0]}).then(response => { - const responseToReturn = Object.fromEntries(Object.entries(response as {availableScene: boolean}).map(responseObj => [responseObj[0].replace('s_', ''), responseObj[1]])) - return responseToReturn as {availableScene: boolean} - }) - return res - } catch (err) { - console.log (err) - return {} - } -} + const [waitingForFootprintSearch, setWaitingForFootprintSearch] = useState(false) const getScenesArray = (sceneString: string): string[] => { const scenesArray = [] @@ -172,7 +214,7 @@ const validateSceneAvailability = async (cycleToUse: number, passToUse: number, return scenesArray } - const setSaveGranulesAlert = (alert: alertMessageInput) => { + const setSaveGranulesAlert = (alert: alertMessageInput, additionalParameters?: any[]) => { const {message, variant} = granuleAlertMessageConstant[alert] dispatch(addGranuleTableAlerts({type: alert, message, variant, tableType: 'granuleSelection' })) } @@ -242,7 +284,7 @@ const validateSceneAvailability = async (cycleToUse: number, passToUse: number, if (authToken === null) { throw new Error('Failed to get authentication token'); } - dispatch(setWaitingForFootprintSearch(true)) + setWaitingForFootprintSearch(true) // convert the tileId in the cps string to a sceneId (divide by 2) const granuleIdTileToSceneArray = granuleId.split('_') let sceneString = String(parseInt(granuleIdTileToSceneArray[2])/2).padStart(3, '0'); @@ -274,10 +316,10 @@ const validateSceneAvailability = async (cycleToUse: number, passToUse: number, return [[], true] } }) - dispatch(setWaitingForFootprintSearch(false)) + setWaitingForFootprintSearch(false) return footprintResult } catch (err) { - dispatch(setWaitingForFootprintSearch(false)) + setWaitingForFootprintSearch(false) console.log (err) if (err instanceof Error) { return err @@ -316,14 +358,13 @@ const validateSceneAvailability = async (cycleToUse: number, passToUse: number, if (!validPass) setSaveGranulesAlert('invalidPass') if (!validScene) setSaveGranulesAlert('invalidScene') return {result: 'first step'} - } else if (addedProducts.length >= granuleTableLimit) { - setSaveGranulesAlert('granuleLimit') - return {result: 'second-step'} - } else { + } + else { const granulesToAdd: allProductParameters[] = [] let someGranulesAlreadyAdded = false let cyclePassSceneSearchParams = searchParams.get('cyclePassScene') ? String(searchParams.get('cyclePassScene')) : '' const sceneArray = getScenesArray(sceneToUse) + let validScenesThatCouldNotBeAdded: string[] = [] // check scenes availability const validationResult = await validateSceneAvailability(parseInt(cycleToUse), parseInt(passToUse), sceneArray.map(sceneId => parseInt(sceneId))).then(scenesAvailable => { // return response @@ -337,31 +378,36 @@ const validateSceneAvailability = async (cycleToUse: number, passToUse: number, }) // TODO: make alert more verbose if some granules are added and others are not when adding more than one with scene hyphen sceneArray.filter(sceneNumber => scenesAvailable[`${cycleToUse}_${passToUse}_${sceneNumber}`]).forEach(async sceneId => { - // check if granule exists with that scene, cycle, and pass - const comboAlreadyAdded = alreadyAddedCyclePassScene(cycleToUse, passToUse, sceneId) - const cyclePassSceneInBounds = checkInBounds('cycle', cycleToUse) && checkInBounds('pass', passToUse) && checkInBounds('scene', sceneId) - if (cyclePassSceneInBounds && !comboAlreadyAdded) { - // get the granuleId from it and pass it to the parameters - const parameters: allProductParameters = { - granuleId: `${cycleToUse}_${passToUse}_${sceneId}`, - name: '', - cycle: cycleToUse, - pass: passToUse, - scene: sceneId, - outputGranuleExtentFlag: parameterOptionValues.outputGranuleExtentFlag.default as number, - outputSamplingGridType: parameterOptionValues.outputSamplingGridType.default as string, - rasterResolution: parameterOptionValues.rasterResolutionUTM.default as number, - utmZoneAdjust: parameterOptionValues.utmZoneAdjust.default as string, - mgrsBandAdjust: parameterOptionValues.mgrsBandAdjust.default as string, - footprint: sampleFootprint - } - // add cycle/pass/scene to url parameters - if (!searchParamSceneComboAlreadyInUrl(cyclePassSceneSearchParams, cycleToUse, passToUse, sceneId)) { - cyclePassSceneSearchParams += `${cyclePassSceneSearchParams.length === 0 ? '' : '-'}${cycleToUse}_${passToUse}_${sceneId}` + if ((granulesToAdd.length + addedProducts.length) >= granuleTableLimit) { + validScenesThatCouldNotBeAdded.push(sceneId) + setSaveGranulesAlert('granuleLimit') + } else { + // check if granule exists with that scene, cycle, and pass + const comboAlreadyAdded = alreadyAddedCyclePassScene(cycleToUse, passToUse, sceneId) + const cyclePassSceneInBounds = checkInBounds('cycle', cycleToUse) && checkInBounds('pass', passToUse) && checkInBounds('scene', sceneId) + if (cyclePassSceneInBounds && !comboAlreadyAdded) { + // get the granuleId from it and pass it to the parameters + const parameters: allProductParameters = { + granuleId: `${cycleToUse}_${passToUse}_${sceneId}`, + name: '', + cycle: cycleToUse, + pass: passToUse, + scene: sceneId, + outputGranuleExtentFlag: parameterOptionValues.outputGranuleExtentFlag.default as number, + outputSamplingGridType: parameterOptionValues.outputSamplingGridType.default as string, + rasterResolution: parameterOptionValues.rasterResolutionUTM.default as number, + utmZoneAdjust: parameterOptionValues.utmZoneAdjust.default as string, + mgrsBandAdjust: parameterOptionValues.mgrsBandAdjust.default as string, + footprint: sampleFootprint + } + // add cycle/pass/scene to url parameters + if (!searchParamSceneComboAlreadyInUrl(cyclePassSceneSearchParams, cycleToUse, passToUse, sceneId)) { + cyclePassSceneSearchParams += `${cyclePassSceneSearchParams.length === 0 ? '' : '-'}${cycleToUse}_${passToUse}_${sceneId}` + } + granulesToAdd.push(parameters) + } else if (comboAlreadyAdded) { + someGranulesAlreadyAdded = true } - granulesToAdd.push(parameters) - } else if (comboAlreadyAdded) { - someGranulesAlreadyAdded = true } }) if (saveType !== 'spatialSearch' && saveType !== 'urlParameter') { @@ -394,8 +440,12 @@ const validateSceneAvailability = async (cycleToUse: number, passToUse: number, // don't run time range check if granule was manually entered if (saveType === 'manual' || saveType === 'urlParameter') { addSearchParamToCurrentUrlState({'cyclePassScene': cyclePassSceneSearchParams}) - if (saveType !== 'urlParameter') { - setSaveGranulesAlert('success') + if (saveType !== 'urlParameter' || startTutorial) { + if (validScenesThatCouldNotBeAdded.length > 0) { + setSaveGranulesAlert('someSuccess') + } else { + setSaveGranulesAlert('success') + } } dispatch(addProduct(productsWithFootprints)) } else { @@ -668,7 +718,9 @@ const validateSceneAvailability = async (cycleToUse: number, passToUse: number, - setCycle(event.target.value)}/> + { + setCycle(event.target.value) + }}/> setPass(event.target.value)}/> setScene(event.target.value)}/> @@ -685,20 +737,23 @@ const validateSceneAvailability = async (cycleToUse: number, passToUse: number,
- {tableType === 'granuleSelection' ? To add multiple scenes at once, enter two numbers into the scene input field separated by a hyphen (e.g. 1-10) : null} {tableType === 'granuleSelection' ? ( + <> + To add multiple scenes at once, enter two numbers into the scene input field separated by a hyphen (e.g. 1-10) {waitingForScenesToBeAdded || waitingForSpatialSearch || waitingForFootprintSearch ? Loading... : - } + {`${addedProducts.length}/${granuleTableLimit} scenes added`} {renderInfoIcon('granuleTableLimit')} + ) : null } diff --git a/src/components/sidebar/SpatialSearchOptions.tsx b/src/components/sidebar/SpatialSearchOptions.tsx index 34394af..8a1c091 100644 --- a/src/components/sidebar/SpatialSearchOptions.tsx +++ b/src/components/sidebar/SpatialSearchOptions.tsx @@ -81,20 +81,8 @@ const SpatialSearchOptions = () => { />
- {/* - - - - */} - {/* -

- Draw areas to search spatially on the map by using the controls on the top right -

-
*/}

Draw areas to search spatially on the map by using the controls on the top right. The scene search will start once you finish drawing the search area shape.

diff --git a/src/components/sidebar/actions/modalSlice.ts b/src/components/sidebar/actions/modalSlice.ts index ae3a743..63d2f17 100644 --- a/src/components/sidebar/actions/modalSlice.ts +++ b/src/components/sidebar/actions/modalSlice.ts @@ -10,6 +10,7 @@ interface AddCustomProductModalState { selectedGranules: string[], showTutorialModal: boolean, skipTutorial: boolean, + showCloseTutorialModal: boolean } // Define the initial state using that type @@ -19,11 +20,12 @@ const initialState: AddCustomProductModalState = { showDeleteProductModal: false, showGenerateProductModal: false, showTutorialModal: false, - skipTutorial: true, + skipTutorial: false, // 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 sampleGranuleDataArray: [], - selectedGranules: [] + selectedGranules: [], + showCloseTutorialModal: false } export const modalSlice = createSlice({ @@ -70,6 +72,12 @@ export const modalSlice = createSlice({ }, setSkipTutorialTrue: (state) => { state.skipTutorial = true + }, + setShowCloseTutorialFalse: (state) => { + state.showCloseTutorialModal = false + }, + setShowCloseTutorialTrue: (state) => { + state.showCloseTutorialModal = true } }, }) @@ -87,7 +95,9 @@ export const { setShowTutorialModalFalse, setShowTutorialModalTrue, setSkipTutorialFalse, - setSkipTutorialTrue + setSkipTutorialTrue, + setShowCloseTutorialFalse, + setShowCloseTutorialTrue, } = modalSlice.actions export default modalSlice.reducer \ No newline at end of file diff --git a/src/components/sidebar/actions/productSlice.ts b/src/components/sidebar/actions/productSlice.ts index a98c05e..ce4ea8e 100644 --- a/src/components/sidebar/actions/productSlice.ts +++ b/src/components/sidebar/actions/productSlice.ts @@ -18,7 +18,6 @@ interface GranuleState { showUTMAdvancedOptions: boolean, spatialSearchResults: SpatialSearchResult[], waitingForSpatialSearch: boolean, - waitingForFootprintSearch: boolean, spatialSearchStartDate: string, spatialSearchEndDate: string, mapFocus: MapFocusObject @@ -26,8 +25,6 @@ interface GranuleState { const {name, cycle, pass, scene, ...generateProductParametersFiltered } = parameterOptionDefaults -const date = new Date() - // Define the initial state using that type const initialState: GranuleState = { // allProducts: this will be like a 'database' for the local state of all the products added @@ -36,7 +33,7 @@ const initialState: GranuleState = { sampleGranuleDataArray: [], selectedGranules: [], granuleFocus: [33.854457, -118.709093], - mapFocus: {center: [33.854457, -118.709093], zoom: 7}, + mapFocus: {center: [33.854457, -118.709093], zoom: 6}, generatedProducts: [], generateProductParameters: generateProductParametersFiltered, granuleTableAlerts: [], @@ -44,7 +41,6 @@ const initialState: GranuleState = { showUTMAdvancedOptions: false, spatialSearchResults: [], waitingForSpatialSearch: false, - waitingForFootprintSearch: false, spatialSearchStartDate: (new Date(2022, 11, 16)).toISOString(), spatialSearchEndDate: (new Date()).toISOString() } @@ -137,9 +133,6 @@ export const productSlice = createSlice({ setWaitingForSpatialSearch: (state, action: PayloadAction) => { state.waitingForSpatialSearch = action.payload }, - setWaitingForFootprintSearch: (state, action: PayloadAction) => { - state.waitingForFootprintSearch = action.payload - }, setSpatialSearchStartDate: (state, action: PayloadAction) => { state.spatialSearchStartDate = action.payload }, @@ -162,7 +155,6 @@ export const { setShowUTMAdvancedOptions, addSpatialSearchResults, setWaitingForSpatialSearch, - setWaitingForFootprintSearch, setSpatialSearchStartDate, setSpatialSearchEndDate, setMapFocus, diff --git a/src/components/tutorial/InteractiveTutorialModalClose.tsx b/src/components/tutorial/InteractiveTutorialModalClose.tsx new file mode 100644 index 0000000..3c7a7c8 --- /dev/null +++ b/src/components/tutorial/InteractiveTutorialModalClose.tsx @@ -0,0 +1,44 @@ +import Button from 'react-bootstrap/Button'; +import Modal from 'react-bootstrap/Modal'; +import { useAppSelector, useAppDispatch } from '../../redux/hooks' +import { setShowCloseTutorialFalse, setSkipTutorialTrue } from '../sidebar/actions/modalSlice' +import { Row } from 'react-bootstrap'; +import { setStartTutorial } from '../app/appSlice'; +import { deleteProduct } from '../sidebar/actions/productSlice'; +import { useNavigate } from 'react-router-dom'; + +const InteractiveTutorialModalClose = () => { + const showCloseTutorialModal = useAppSelector((state) => state.modal.showCloseTutorialModal) + const dispatch = useAppDispatch() + const navigate = useNavigate() + const addedProducts = useAppSelector((state) => state.product.addedProducts) + + const handleCloseTutorial = () => { + dispatch(setSkipTutorialTrue()) + dispatch(setStartTutorial(false)) + dispatch(deleteProduct(addedProducts.map(product => product.granuleId))) + navigate(`/customizeProduct/selectScenes`) + dispatch(setShowCloseTutorialFalse()) + } + + return ( + dispatch(setShowCloseTutorialFalse())}> + + Exit Interactive Tutorial + + + + +
Are you sure you want to exit the tutorial?
+
+
+ + + + + +
+ ); +} + +export default InteractiveTutorialModalClose; \ No newline at end of file diff --git a/src/components/tutorial/tutorialConstants.ts b/src/components/tutorial/tutorialConstants.ts index 41c93e5..0647899 100644 --- a/src/components/tutorial/tutorialConstants.ts +++ b/src/components/tutorial/tutorialConstants.ts @@ -138,7 +138,7 @@ export const tutorialSteps = [ }, { target: "#scenes-to-customize", - content: "This is this table showing the scenes you can customize. Also shown are the options which are scene specific which are not applied to all the scenes in the list.", + content: "This is the table showing the scenes you can customize. Also shown are the options which are scene specific which are not applied to all the scenes in the list.", disableBeacon: true, styles: { options: { diff --git a/src/components/welcome/Welcome.tsx b/src/components/welcome/Welcome.tsx index 75eefc5..908e1ec 100644 --- a/src/components/welcome/Welcome.tsx +++ b/src/components/welcome/Welcome.tsx @@ -29,7 +29,7 @@ const Welcome = () => { return ( -

SWOT Level-2 On-demand Raster Generator

+

SWOT On-Demand Level-2 Raster Generator

diff --git a/src/constants/rasterParameterConstants.ts b/src/constants/rasterParameterConstants.ts index fad2e10..322db77 100644 --- a/src/constants/rasterParameterConstants.ts +++ b/src/constants/rasterParameterConstants.ts @@ -95,6 +95,8 @@ export const parameterOptionDefaults = { mgrsBandAdjust: '0', } +export const granuleTableLimit = 10 + export const parameterHelp: ParameterHelp = { 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.`, @@ -104,7 +106,8 @@ export const parameterHelp: ParameterHelp = { cycle: `The repeat orbit cycle number of the observation. SWOT’s orbit is 21 days and thus observations in the same 21-day orbit period would have the same cycle number.`, pass: `Predefined sections of the orbit between the maximum and minimum latitudes. SWOT has 584 passes in one cycle, split into ascending and descending passes`, scene: `Predefined 128 x 128 km squares of the SWOT observations.`, - status: `The processing status of your custom product. The status types are as follows: NEW, UNAVAILABLE, GENERATING, ERROR, READY, AVAILABLE` + status: `The processing status of your custom product. The status types are as follows: NEW, UNAVAILABLE, GENERATING, ERROR, READY, AVAILABLE`, + granuleTableLimit: `There is a limit of ${granuleTableLimit} scenes allowed to be added to the scene table at a time. This is to ensure our scene processing pipeline can handle the demand of all of SWODLR's users.` } export interface InputBounds { @@ -129,8 +132,6 @@ scene: { } } -export const granuleTableLimit = 10 - export const granuleAlertMessageConstant: granuleAlertMessageConstantType = { success: { message: 'Successfully added scenes!', @@ -177,12 +178,24 @@ export const granuleAlertMessageConstant: granuleAlertMessageConstantType = { variant: 'danger', }, granuleLimit: { - message: `You can only process ${granuleTableLimit} scenes at a time.`, + message: `You can only process ${granuleTableLimit} scenes at a time so some scenes could not be added.`, variant: 'danger' }, notInTimeRange: { message: `Some scenes were not within the specified spatial search time range.`, variant: 'danger' + }, + someSuccess: { + message: `Successfully added some scenes.`, + variant: 'success' + }, + successfullyGenerated: { + message: `Successfully started product generation! Go to the 'My Data' page to track progress.`, + variant: 'success' + }, + spatialSearchAreaTooLarge: { + message: `The search area you've selected on the map is too large. Please choose a smaller area to search.`, + variant: 'warning' } } diff --git a/src/types/constantTypes.ts b/src/types/constantTypes.ts index b61f34a..65cefa4 100644 --- a/src/types/constantTypes.ts +++ b/src/types/constantTypes.ts @@ -137,7 +137,7 @@ export interface validScene { [key: string]: boolean } -export type alertMessageInput = 'success' | 'alreadyAdded' | 'allScenesNotAvailable' | 'alreadyAddedAndNotFound' | 'noScenesAdded' | 'readyForGeneration' | 'invalidCycle' | 'invalidPass' | 'invalidScene' | 'invalidScene' | 'someScenesNotAvailable' | 'granuleLimit' | 'notInTimeRange' | 'noScenesFound' +export type alertMessageInput = 'success' | 'alreadyAdded' | 'allScenesNotAvailable' | 'alreadyAddedAndNotFound' | 'noScenesAdded' | 'readyForGeneration' | 'invalidCycle' | 'invalidPass' | 'invalidScene' | 'invalidScene' | 'someScenesNotAvailable' | 'granuleLimit' | 'notInTimeRange' | 'noScenesFound' | 'someSuccess' | 'successfullyGenerated' | 'spatialSearchAreaTooLarge' export interface SpatialSearchResult { cycle: string, From 36dd0469500994b9715e9ccadc951d71b231c3ea Mon Sep 17 00:00:00 2001 From: cqbanh Date: Wed, 27 Mar 2024 08:16:40 +0000 Subject: [PATCH 14/60] Deploying --- CHANGE | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGE b/CHANGE index acca8be..c04e256 100644 --- a/CHANGE +++ b/CHANGE @@ -1 +1,2 @@ podaac/swodlr-ui deployment +podaac/swodlr-ui deployment From 84c0eba27ad7bf94149a0c3041d6a9ac2956c2f7 Mon Sep 17 00:00:00 2001 From: "podaac-cicd[bot]" Date: Wed, 27 Mar 2024 09:18:30 +0000 Subject: [PATCH 15/60] /version v1.0.1-rc.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a26521c..b739301 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.0.0", + "version": "1.0.1-rc.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.0.0", + "version": "1.0.1-rc.1", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index 910daf8..415f03b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.0.0", + "version": "1.0.1-rc.1", "private": true, "engines": { "node": ">=18.0.0" From 3d4f2e31deb6b78a26f725284b0918051ab834a4 Mon Sep 17 00:00:00 2001 From: cqbanh Date: Wed, 27 Mar 2024 09:52:21 +0000 Subject: [PATCH 16/60] Deploying --- CHANGE | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGE b/CHANGE index acca8be..c04e256 100644 --- a/CHANGE +++ b/CHANGE @@ -1 +1,2 @@ podaac/swodlr-ui deployment +podaac/swodlr-ui deployment From ea9cf6dc42c0c2c98ef4fb9aa2206cc5f5dd4783 Mon Sep 17 00:00:00 2001 From: "podaac-cicd[bot]" Date: Wed, 27 Mar 2024 09:54:26 +0000 Subject: [PATCH 17/60] /version v1.0.1-rc.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index b739301..69f5d89 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.0.1-rc.1", + "version": "1.0.1-rc.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.0.1-rc.1", + "version": "1.0.1-rc.2", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index 415f03b..cb32388 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.0.1-rc.1", + "version": "1.0.1-rc.2", "private": true, "engines": { "node": ">=18.0.0" From c6eebc0acc3b168ee24dc814c8e7e54c6a0774dc Mon Sep 17 00:00:00 2001 From: cqbanh Date: Wed, 27 Mar 2024 10:07:58 +0000 Subject: [PATCH 18/60] Deploying --- CHANGE | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGE b/CHANGE index c04e256..cdb6bd1 100644 --- a/CHANGE +++ b/CHANGE @@ -1,2 +1,3 @@ podaac/swodlr-ui deployment podaac/swodlr-ui deployment +podaac/swodlr-ui deployment From 262e8b77ebae87d89a8da5d56e34d90f4ada1bae Mon Sep 17 00:00:00 2001 From: "podaac-cicd[bot]" Date: Wed, 27 Mar 2024 10:10:15 +0000 Subject: [PATCH 19/60] /version v1.0.1-rc.3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 69f5d89..f0c3e59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.0.1-rc.2", + "version": "1.0.1-rc.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.0.1-rc.2", + "version": "1.0.1-rc.3", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index cb32388..a6b2812 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.0.1-rc.2", + "version": "1.0.1-rc.3", "private": true, "engines": { "node": ">=18.0.0" From 8dd06268f8314bfc153e557307ea7172e249b1f1 Mon Sep 17 00:00:00 2001 From: cqbanh Date: Wed, 27 Mar 2024 10:18:02 +0000 Subject: [PATCH 20/60] Deploying --- CHANGE | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGE b/CHANGE index cdb6bd1..e79f9a3 100644 --- a/CHANGE +++ b/CHANGE @@ -1,3 +1,4 @@ podaac/swodlr-ui deployment podaac/swodlr-ui deployment podaac/swodlr-ui deployment +podaac/swodlr-ui deployment From 569a04f8eb7cb67a52aae2c3b375e2dccb8997de Mon Sep 17 00:00:00 2001 From: "podaac-cicd[bot]" Date: Wed, 27 Mar 2024 10:20:04 +0000 Subject: [PATCH 21/60] /version v1.0.1-rc.4 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index f0c3e59..e29d640 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.0.1-rc.3", + "version": "1.0.1-rc.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.0.1-rc.3", + "version": "1.0.1-rc.4", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index a6b2812..949eab2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.0.1-rc.3", + "version": "1.0.1-rc.4", "private": true, "engines": { "node": ">=18.0.0" From 809b959f13c2fa33762d1a03faab920c913ef1fa Mon Sep 17 00:00:00 2001 From: cqbanh Date: Wed, 27 Mar 2024 10:47:03 +0000 Subject: [PATCH 22/60] Deploying --- CHANGE | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGE b/CHANGE index e79f9a3..40060af 100644 --- a/CHANGE +++ b/CHANGE @@ -2,3 +2,4 @@ podaac/swodlr-ui deployment podaac/swodlr-ui deployment podaac/swodlr-ui deployment podaac/swodlr-ui deployment +podaac/swodlr-ui deployment From fb8647b8739d90b622504350626fc88d1e960bf2 Mon Sep 17 00:00:00 2001 From: "podaac-cicd[bot]" Date: Wed, 27 Mar 2024 10:49:02 +0000 Subject: [PATCH 23/60] /version v1.0.1-rc.5 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e29d640..bfaf3be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.0.1-rc.4", + "version": "1.0.1-rc.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.0.1-rc.4", + "version": "1.0.1-rc.5", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index 949eab2..0049a09 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.0.1-rc.4", + "version": "1.0.1-rc.5", "private": true, "engines": { "node": ">=18.0.0" From d07a8d5155c0cb82499837396ddb2108e3b23ba7 Mon Sep 17 00:00:00 2001 From: cqbanh Date: Wed, 27 Mar 2024 11:04:47 +0000 Subject: [PATCH 24/60] Deploying --- CHANGE | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGE b/CHANGE index 40060af..7382246 100644 --- a/CHANGE +++ b/CHANGE @@ -3,3 +3,4 @@ podaac/swodlr-ui deployment podaac/swodlr-ui deployment podaac/swodlr-ui deployment podaac/swodlr-ui deployment +podaac/swodlr-ui deployment From 46553bdfac7dbc1c12f43d6b1a25f363f4384b54 Mon Sep 17 00:00:00 2001 From: "podaac-cicd[bot]" Date: Wed, 27 Mar 2024 11:07:13 +0000 Subject: [PATCH 25/60] /version v1.0.1-rc.6 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index bfaf3be..3f38d16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.0.1-rc.5", + "version": "1.0.1-rc.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.0.1-rc.5", + "version": "1.0.1-rc.6", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index 0049a09..d71044b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.0.1-rc.5", + "version": "1.0.1-rc.6", "private": true, "engines": { "node": ">=18.0.0" From e30ac3d85f190ce6a8757c7bdd4adfb5053307e1 Mon Sep 17 00:00:00 2001 From: cqbanh Date: Wed, 27 Mar 2024 11:34:00 +0000 Subject: [PATCH 26/60] Deploying --- CHANGE | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGE b/CHANGE index 7382246..6e98b85 100644 --- a/CHANGE +++ b/CHANGE @@ -4,3 +4,4 @@ podaac/swodlr-ui deployment podaac/swodlr-ui deployment podaac/swodlr-ui deployment podaac/swodlr-ui deployment +podaac/swodlr-ui deployment From 47dc7aad4615e804ee443b02c2aeb8a2cb059ac0 Mon Sep 17 00:00:00 2001 From: "podaac-cicd[bot]" Date: Wed, 27 Mar 2024 11:36:40 +0000 Subject: [PATCH 27/60] /version v1.0.1-rc.7 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3f38d16..e20aa1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.0.1-rc.6", + "version": "1.0.1-rc.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.0.1-rc.6", + "version": "1.0.1-rc.7", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index d71044b..4ce0b49 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.0.1-rc.6", + "version": "1.0.1-rc.7", "private": true, "engines": { "node": ">=18.0.0" From 79d3884954e32e9c2a83e01f9b0509375d9e6d7f Mon Sep 17 00:00:00 2001 From: cqbanh Date: Wed, 27 Mar 2024 11:44:43 +0000 Subject: [PATCH 28/60] Deploying --- CHANGE | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGE b/CHANGE index 6e98b85..fd26789 100644 --- a/CHANGE +++ b/CHANGE @@ -5,3 +5,4 @@ podaac/swodlr-ui deployment podaac/swodlr-ui deployment podaac/swodlr-ui deployment podaac/swodlr-ui deployment +podaac/swodlr-ui deployment From 398afefd001437e8d5738783ff9adb0889e371be Mon Sep 17 00:00:00 2001 From: "podaac-cicd[bot]" Date: Wed, 27 Mar 2024 11:46:37 +0000 Subject: [PATCH 29/60] /version v1.0.1-rc.8 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e20aa1b..ca9b387 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.0.1-rc.7", + "version": "1.0.1-rc.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.0.1-rc.7", + "version": "1.0.1-rc.8", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index 4ce0b49..0d74ad8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.0.1-rc.7", + "version": "1.0.1-rc.8", "private": true, "engines": { "node": ">=18.0.0" From e9f9af01d4c343eee6425a4f44fe60733fa6bc83 Mon Sep 17 00:00:00 2001 From: cqbanh Date: Wed, 27 Mar 2024 12:07:09 +0000 Subject: [PATCH 30/60] Deploying --- CHANGE | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGE b/CHANGE index fd26789..cfde925 100644 --- a/CHANGE +++ b/CHANGE @@ -6,3 +6,4 @@ podaac/swodlr-ui deployment podaac/swodlr-ui deployment podaac/swodlr-ui deployment podaac/swodlr-ui deployment +podaac/swodlr-ui deployment From 09b61b8bc8d5b11fdb9d5d2a10432e57980d2103 Mon Sep 17 00:00:00 2001 From: "podaac-cicd[bot]" Date: Wed, 27 Mar 2024 12:09:39 +0000 Subject: [PATCH 31/60] /version v1.0.1-rc.9 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ca9b387..9b8ead2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.0.1-rc.8", + "version": "1.0.1-rc.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.0.1-rc.8", + "version": "1.0.1-rc.9", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index 0d74ad8..aab1cae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.0.1-rc.8", + "version": "1.0.1-rc.9", "private": true, "engines": { "node": ">=18.0.0" From ad0a97d5b94fa99986ab52b9d49d6cf9d642c3fd Mon Sep 17 00:00:00 2001 From: cqbanh Date: Wed, 27 Mar 2024 12:14:25 +0000 Subject: [PATCH 32/60] Deploying --- CHANGE | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGE b/CHANGE index cfde925..e4fc398 100644 --- a/CHANGE +++ b/CHANGE @@ -7,3 +7,4 @@ podaac/swodlr-ui deployment podaac/swodlr-ui deployment podaac/swodlr-ui deployment podaac/swodlr-ui deployment +podaac/swodlr-ui deployment From 88f6b8745e89d440136f4c0c6bbc2137ad0fdda1 Mon Sep 17 00:00:00 2001 From: "podaac-cicd[bot]" Date: Wed, 27 Mar 2024 12:16:46 +0000 Subject: [PATCH 33/60] /version v1.0.1-rc.10 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9b8ead2..8d81024 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.0.1-rc.9", + "version": "1.0.1-rc.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.0.1-rc.9", + "version": "1.0.1-rc.10", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index aab1cae..604eeee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.0.1-rc.9", + "version": "1.0.1-rc.10", "private": true, "engines": { "node": ">=18.0.0" From f9d4de2a8be132bbae2a218c449555f07c70286c Mon Sep 17 00:00:00 2001 From: "podaac-cicd[bot]" Date: Wed, 27 Mar 2024 12:25:38 +0000 Subject: [PATCH 34/60] /version 1.0.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8d81024..8acb0e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.0.1-rc.10", + "version": "1.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.0.1-rc.10", + "version": "1.0.1", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index 604eeee..f77658e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.0.1-rc.10", + "version": "1.0.1", "private": true, "engines": { "node": ">=18.0.0" From 84b6f6f62c14920016c75256147a4508ae9a5102 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Tue, 11 Jun 2024 20:02:15 -0700 Subject: [PATCH 35/60] Feature/swodlr UI 107 - gray out some product customization options (#110) * /version v1.0.0-rc.1 * /version v1.0.0-rc.2 * Pull in recent changes to 1.0.0 * /version v1.0.0-rc.3 * Fix umm-t record * /version v1.0.0-rc.4 * Fix umm-t record * /version v1.0.0-rc.5 * Fix umm-t record * /version v1.0.0-rc.6 * /version v1.0.0-rc.7 * issue/manual-granule-input-hotfix: fixed how ranged of scenes are processed (#86) Co-authored-by: jbyrne * Issues/swodlr UI 72 essential UI bug fixes (#87) * issues/swodlr-ui-72: fix cps url params bug * issues/swodlr-ui-72 * issues/swodlr-ui-75: fixed a couple bugs * issues/swodlr-ui-72: fixed map movement, adjust options, data page limit, etc * issues/swodlr-ui-72: changed spatial search beginning date in range * issues/swodlr-ui-72-essential-fixes: cleaned up comments --------- Co-authored-by: jbyrne * /version v1.0.0-rc.8 * feature/demo-uwg-june2024: fix tutorial popup * feature/demo-uwg-june2024: fix my data page loading bug * feature/demo-uwg-june2024: fix my data page no data navigation * feature/swodlr-ui-107: grey out some product customization options * feature/swodlr-ui-107: added info alert about disabled parameter options * feature/swodlr-ui-107: added feature not available tooltip * feature/swodlr-ui-107: change feature not available alert message --------- Co-authored-by: frankinspace Co-authored-by: Frank Greguska Co-authored-by: Jonathan M Smolenski Co-authored-by: jonathansmolenski Co-authored-by: jbyrne --- .../sidebar/ProductCustomization.tsx | 60 +++++++++++++++---- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/src/components/sidebar/ProductCustomization.tsx b/src/components/sidebar/ProductCustomization.tsx index 9b7ad43..1bfa89d 100644 --- a/src/components/sidebar/ProductCustomization.tsx +++ b/src/components/sidebar/ProductCustomization.tsx @@ -1,13 +1,15 @@ import { useEffect } from 'react'; import { useAppDispatch, useAppSelector } from '../../redux/hooks' import Form from 'react-bootstrap/Form'; -import { Col, OverlayTrigger, Row, Tooltip } from 'react-bootstrap'; +import { Alert, Col, OverlayTrigger, Row, Tooltip } from 'react-bootstrap'; import { parameterHelp, parameterOptionValues } from '../../constants/rasterParameterConstants' import { InfoCircle } from 'react-bootstrap-icons'; import { setGenerateProductParameters, setShowUTMAdvancedOptions } from "./actions/productSlice"; import { useSearchParams } from 'react-router-dom'; import { GenerateProductParameters } from '../../types/constantTypes'; +// TODO: revert back after ONLY RESOLUTION SELECTION period is over. Search todos related to disabled and featureNotAvailable + const ProductCustomization = () => { const colorModeClass = useAppSelector((state) => state.navbar.colorModeClass) const generateProductParameters = useAppSelector((state) => state.product.generateProductParameters) @@ -149,20 +151,40 @@ const ProductCustomization = () => { ) + const renderFeatureNotAvailable = (jsxContents: JSX.Element, featureDisabled: boolean) => { + return featureDisabled ? + Option not currently available. + } + > + {{jsxContents}} + + : jsxContents + } + const renderOutputSamplingGridTypeInputs = (outputSamplingGridType: string) => { const inputArray = parameterOptionValues.outputSamplingGridType.values.map((value, index) => { + // TODO: Remove featureNotAvailable after ONLY RESOLUTION SELECTION period is over + const featureNotAvailable = value === 'lat/lon' const resolutionToUse: number = value === 'utm' ? rasterResolutionUTM : rasterResolutionGEO return ( - setOutputSamplingGridType(value as string, resolutionToUse)} - key={`outputSamplingGridTypeGroup-radio-key-${index}`} - /> + renderFeatureNotAvailable( + setOutputSamplingGridType(value as string, resolutionToUse)} + key={`outputSamplingGridTypeGroup-radio-key-${index}`} + />, + featureNotAvailable + ) )} ) inputArray.push( @@ -198,17 +220,24 @@ const ProductCustomization = () => { {parameterOptionValues.outputGranuleExtentFlag.values.map((value, index) => { + // TODO: Remove featureNotAvailable after ONLY RESOLUTION SELECTION period is over + const featureNotAvailable: boolean = Boolean(value) return ( + renderFeatureNotAvailable( setOutputGranuleExtentFlag(value)} key={`outputGranuleExtentFlagTypeGroup-radio-key-${index}`} - /> + />, + featureNotAvailable + ) ) })} @@ -236,6 +265,13 @@ const ProductCustomization = () => { {renderRasterResolutionUnits(outputSamplingGridType)} + + + + Some options are not enabled in the current version of SWODLR and will be enabled at a later date + + + ); From af393ed08abbb87908a744286a6bfbe4a333b8a5 Mon Sep 17 00:00:00 2001 From: PODAAC-CICD Date: Wed, 12 Jun 2024 03:04:09 +0000 Subject: [PATCH 36/60] /version v1.0.1-2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4121754..c5a1a32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.0.1-1", + "version": "1.0.1-2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.0.1-1", + "version": "1.0.1-2", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index 5f31da9..25b121f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.0.1-1", + "version": "1.0.1-2", "private": true, "engines": { "node": ">=18.0.0" From 4d283c4081871a3f5103194975f4d8124e60da2d Mon Sep 17 00:00:00 2001 From: Jimmy Date: Wed, 12 Jun 2024 09:17:40 -0700 Subject: [PATCH 37/60] Feature/swodlr UI sci orbit message (#112) * /version v1.0.0-rc.1 * /version v1.0.0-rc.2 * Pull in recent changes to 1.0.0 * /version v1.0.0-rc.3 * Fix umm-t record * /version v1.0.0-rc.4 * Fix umm-t record * /version v1.0.0-rc.5 * Fix umm-t record * /version v1.0.0-rc.6 * /version v1.0.0-rc.7 * issue/manual-granule-input-hotfix: fixed how ranged of scenes are processed (#86) Co-authored-by: jbyrne * Issues/swodlr UI 72 essential UI bug fixes (#87) * issues/swodlr-ui-72: fix cps url params bug * issues/swodlr-ui-72 * issues/swodlr-ui-75: fixed a couple bugs * issues/swodlr-ui-72: fixed map movement, adjust options, data page limit, etc * issues/swodlr-ui-72: changed spatial search beginning date in range * issues/swodlr-ui-72-essential-fixes: cleaned up comments --------- Co-authored-by: jbyrne * /version v1.0.0-rc.8 * feature/demo-uwg-june2024: fix tutorial popup * feature/demo-uwg-june2024: fix my data page loading bug * feature/demo-uwg-june2024: fix my data page no data navigation * feature/swodlr-ui-107: grey out some product customization options * feature/swodlr-ui-107: added info alert about disabled parameter options * feature/swodlr-ui-107: added feature not available tooltip * feature/swodlr-ui-107: change feature not available alert message * feature/swodlr-ui-sci-orbiit-message: added message for only sci orbits * feature/swodlr-ui-sci-orbit-message: update cycle to 1-399 --------- Co-authored-by: frankinspace Co-authored-by: Frank Greguska Co-authored-by: Jonathan M Smolenski Co-authored-by: jonathansmolenski Co-authored-by: jbyrne --- src/components/sidebar/GranulesTable.tsx | 7 +++++-- src/constants/rasterParameterConstants.ts | 9 ++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/components/sidebar/GranulesTable.tsx b/src/components/sidebar/GranulesTable.tsx index cdf13bf..aa33d3b 100644 --- a/src/components/sidebar/GranulesTable.tsx +++ b/src/components/sidebar/GranulesTable.tsx @@ -6,7 +6,7 @@ import { granuleAlertMessageConstant, granuleSelectionLabels, productCustomizati afterCPSL, afterCPSR, spatialSearchCollectionConceptId} from '../../constants/rasterParameterConstants'; -import { Button, Col, Form, OverlayTrigger, Row, Tooltip, Spinner } from 'react-bootstrap'; +import { Button, Col, Form, OverlayTrigger, Row, Tooltip, Spinner, Alert } from 'react-bootstrap'; import { InfoCircle, Plus, Trash } from 'react-bootstrap-icons'; import { AdjustType, AdjustValueDecoder, GranuleForTable, GranuleTableProps, InputType, SaveType, SpatialSearchResult, TableTypes, alertMessageInput, allProductParameters, handleSaveResult, validScene } from '../../types/constantTypes'; import { addProduct, setSelectedGranules, setGranuleFocus, addGranuleTableAlerts, editProduct, addSpatialSearchResults, clearGranuleTableAlerts, setWaitingForSpatialSearch } from './actions/productSlice'; @@ -740,7 +740,7 @@ const GranuleTable = (props: GranuleTableProps) => { setScene(event.target.value)}/> - Valid Values: + Valid Values: {renderInfoIcon('validCPSValues')} {`${inputBounds.cycle.min} - ${inputBounds.cycle.max}`} {`${inputBounds.pass.min} - ${inputBounds.pass.max}`} {`${inputBounds.scene.min} - ${inputBounds.scene.max}`} @@ -755,6 +755,9 @@ const GranuleTable = (props: GranuleTableProps) => { {tableType === 'granuleSelection' ? ( <> To add multiple scenes at once, enter two numbers into the scene input field separated by a hyphen (e.g. 1-10) + + Only scientific orbit cycle, pass, scene values are supported at this time. + {waitingForScenesToBeAdded || waitingForSpatialSearch || waitingForFootprintSearch ? diff --git a/src/constants/rasterParameterConstants.ts b/src/constants/rasterParameterConstants.ts index 408fe36..79b40d9 100644 --- a/src/constants/rasterParameterConstants.ts +++ b/src/constants/rasterParameterConstants.ts @@ -108,7 +108,8 @@ export const parameterHelp: ParameterHelp = { pass: `Predefined sections of the orbit between the maximum and minimum latitudes. SWOT has 584 passes in one cycle, split into ascending and descending passes`, scene: `Predefined 128 x 128 km squares of the SWOT observations.`, status: `The processing status of your custom product. The status types are as follows: NEW, UNAVAILABLE, GENERATING, ERROR, READY, AVAILABLE`, - granuleTableLimit: `There is a limit of ${granuleTableLimit} scenes allowed to be added to the scene table at a time. This is to ensure our scene processing pipeline can handle the demand of all of SWODLR's users.` + granuleTableLimit: `There is a limit of ${granuleTableLimit} scenes allowed to be added to the scene table at a time. This is to ensure our scene processing pipeline can handle the demand of all of SWODLR's users.`, + validCPSValues: `There are two types of orbits, calibration and scientific. Calibration orbits have a 400-578 cycle range and science orbits have a 0-399 cycle range. Cycles in the calibration orbit range are not currently supported at this time but will be in the future.`, } export interface InputBounds { @@ -118,10 +119,12 @@ export interface InputBounds { } } +// cycle for calibration orbit is 400-578 +// TODO: change cycle max back to 578 when calibration orbit is implemented export const inputBounds: inputValuesDictionary = { cycle: { - min: 0, - max: 578 + min: 1, + max: 399 }, pass: { min: 1, From b1d205fa3dc48e46ce8ad2e284247a1657bbfec3 Mon Sep 17 00:00:00 2001 From: PODAAC-CICD Date: Wed, 12 Jun 2024 16:19:44 +0000 Subject: [PATCH 38/60] /version v1.0.1-3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c5a1a32..3251e4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.0.1-2", + "version": "1.0.1-3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.0.1-2", + "version": "1.0.1-3", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index 25b121f..fc3eaf9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.0.1-2", + "version": "1.0.1-3", "private": true, "engines": { "node": ">=18.0.0" From c7b7278b911c243aa32662fba0ee5264c99e3a8d Mon Sep 17 00:00:00 2001 From: Jimmy Date: Thu, 13 Jun 2024 16:10:02 -0700 Subject: [PATCH 39/60] Feature/swodlr UI 102 - refactor spatial search validation (#111) * /version v1.0.0-rc.1 * /version v1.0.0-rc.2 * Pull in recent changes to 1.0.0 * /version v1.0.0-rc.3 * Fix umm-t record * /version v1.0.0-rc.4 * Fix umm-t record * /version v1.0.0-rc.5 * Fix umm-t record * /version v1.0.0-rc.6 * /version v1.0.0-rc.7 * issue/manual-granule-input-hotfix: fixed how ranged of scenes are processed (#86) Co-authored-by: jbyrne * Issues/swodlr UI 72 essential UI bug fixes (#87) * issues/swodlr-ui-72: fix cps url params bug * issues/swodlr-ui-72 * issues/swodlr-ui-75: fixed a couple bugs * issues/swodlr-ui-72: fixed map movement, adjust options, data page limit, etc * issues/swodlr-ui-72: changed spatial search beginning date in range * issues/swodlr-ui-72-essential-fixes: cleaned up comments --------- Co-authored-by: jbyrne * /version v1.0.0-rc.8 * feature/demo-uwg-june2024: fix tutorial popup * feature/demo-uwg-june2024: fix my data page loading bug * feature/demo-uwg-june2024: fix my data page no data navigation * feature/swodlr-ui-107: grey out some product customization options * feature/swodlr-ui-107: added info alert about disabled parameter options * feature/swodlr-ui-107: added feature not available tooltip * feature/swodlr-ui-107: change feature not available alert message * feature/swodlr-ui-102: refactor footprint validation * feature/swodlr-ui-102: optimized spatial search * feature/swodlr-ui-102: cleaned up console log * feature/swodlr-ui-102: add utm zone parameter * feature/swodlr-ui-102: get grayed out config options merged in --------- Co-authored-by: frankinspace Co-authored-by: Frank Greguska Co-authored-by: Jonathan M Smolenski Co-authored-by: jonathansmolenski Co-authored-by: jbyrne --- src/components/history/HistoryFilters.tsx | 2 +- src/components/map/WorldMap.tsx | 67 ++- src/components/sidebar/GranulesTable.tsx | 494 +++++++++++----------- src/constants/graphqlQueries.ts | 60 ++- src/constants/rasterParameterConstants.ts | 24 -- src/types/constantTypes.ts | 37 +- 6 files changed, 395 insertions(+), 289 deletions(-) diff --git a/src/components/history/HistoryFilters.tsx b/src/components/history/HistoryFilters.tsx index bdb748f..b876802 100644 --- a/src/components/history/HistoryFilters.tsx +++ b/src/components/history/HistoryFilters.tsx @@ -149,7 +149,7 @@ const HistoryFilters = () => { Cycle - + {}} initialValues={{}}>
handleChangeFilters('cycle', String(e.target.value), !cycleIsInvalid)}/> diff --git a/src/components/map/WorldMap.tsx b/src/components/map/WorldMap.tsx index 55544ec..cb9a9a6 100644 --- a/src/components/map/WorldMap.tsx +++ b/src/components/map/WorldMap.tsx @@ -14,6 +14,7 @@ import { addSpatialSearchResults, setMapFocus, setWaitingForSpatialSearch } from import { SpatialSearchResult } from '../../types/constantTypes'; import { useLocation, useSearchParams } from 'react-router-dom'; import { useEffect } from 'react'; +import { getGranules, getSpatialSearchGranuleVariables } from '../../constants/graphqlQueries'; let DefaultIcon = L.icon({ iconUrl: icon, @@ -105,34 +106,58 @@ const WorldMap = () => { } // create string with polygon array - let polygonString = '&polygon[]=' + // let polygonString = '&polygon[]=' + let polygonString = '' polygonCoordinates.forEach((lngLatPair, index) => { polygonString += `${index === 0 ? '' : ',' }${lngLatPair[0]},${lngLatPair[1]}` }) return polygonString }).join() - const spatialSearchUrl = `https://cmr.earthdata.nasa.gov/search/granules?collection_concept_id=${spatialSearchCollectionConceptId}${polygonUrlString}&page_size=${spatialSearchResultLimit}` - const spatialSearchResponse = await fetch(spatialSearchUrl, { - method: 'GET', - credentials: 'omit', + + const spatialSearchResponse = await fetch('https://graphql.earthdata.nasa.gov/api', { + method: 'POST', headers: { - Authorization: `Bearer ${authToken}` - } + Authorization: `Bearer ${authToken}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ query: getGranules, variables: getSpatialSearchGranuleVariables(polygonUrlString, spatialSearchCollectionConceptId, spatialSearchResultLimit) }) }).then(async data => { - const responseText = await data.text() + const responseJson = await data.json() // TODO: make subsequent calls to get granules in spatial search area till everything is found. - // current issue is that 1000 (2000 total divided by 2) is limited by the cmr api. - const parser = new DOMParser(); - const xml = parser.parseFromString(responseText, "application/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 updatedGranules = responseJson.data.granules.items.map((item: any) => { + const itemCopy = structuredClone(item) + const cpsString = item.granuleUr.match(`${beforeCPS}([0-9]+(_[0-9]+)+)(${afterCPSR}|${afterCPSL})`)?.[1] + itemCopy.cpsString = cpsString + return itemCopy + }) + const cpsStringTracker: string[] = [] + const updatedGranulesToUse = updatedGranules.filter((updatedGranuleObject: any) => { + // if cpsString not in tracker, it has not been repeated yet. Add to tracker and return + const granuleRepeated = cpsStringTracker.includes(updatedGranuleObject.cpsString) + if(!granuleRepeated) cpsStringTracker.push(updatedGranuleObject.cpsString) + return !granuleRepeated + // if cpsString in tracker, it has been repeated. Do not return + }) + const spatialSearchResults = updatedGranulesToUse.map((updatedGranuleObject: any) => { + const {producerGranuleId, granuleUr, cpsString, polygons, timeStart, timeEnd} = updatedGranuleObject + console.log(granuleUr) + const cyclePassSceneStringArray = cpsString.split('_').map((id: string) => parseInt(id).toString()) const tileValue = parseInt(cyclePassSceneStringArray?.[2] as string) const sceneToUse = String(Math.floor(tileValue)) - return {cycle: cyclePassSceneStringArray?.[0], pass: cyclePassSceneStringArray?.[1], scene : sceneToUse} as SpatialSearchResult + const returnObject: SpatialSearchResult = { + cycle: cyclePassSceneStringArray?.[0], + pass: cyclePassSceneStringArray?.[1], + scene : sceneToUse, + producerGranuleId, + granuleUr, + timeStart, + timeEnd, + polygons + } + return returnObject }) - return references + return spatialSearchResults }) dispatch(addSpatialSearchResults(spatialSearchResponse as SpatialSearchResult[])) } catch (err) { @@ -206,7 +231,13 @@ const WorldMap = () => { {addedProducts.map((productObject, index) => ( - {[
{`Cycle: ${productObject.cycle}`}
,
{`Pass: ${productObject.pass}`}
,
{`Scene: ${productObject.scene}`}
,
{`File Name: ${productObject.fileName}`}
]}
+ {[ +
{`Cycle: ${productObject.cycle}`}
, +
{`Pass: ${productObject.pass}`}
, +
{`Scene: ${productObject.scene}`}
, +
{`UTM Zone: ${productObject.utmZone}`}
, +
{`File Name: ${productObject.producerGranuleId}`}
+ ]}
))} diff --git a/src/components/sidebar/GranulesTable.tsx b/src/components/sidebar/GranulesTable.tsx index aa33d3b..fc52973 100644 --- a/src/components/sidebar/GranulesTable.tsx +++ b/src/components/sidebar/GranulesTable.tsx @@ -1,14 +1,13 @@ import { ReactElement, useEffect, useState } from 'react'; import Table from 'react-bootstrap/Table'; import { useAppSelector, useAppDispatch } from '../../redux/hooks' -import { granuleAlertMessageConstant, granuleSelectionLabels, productCustomizationLabelsUTM, productCustomizationLabelsGEO, parameterOptionValues, parameterHelp, infoIconsToRender, inputBounds, sampleFootprint, granuleTableLimit, +import { granuleAlertMessageConstant, granuleSelectionLabels, productCustomizationLabelsUTM, productCustomizationLabelsGEO, parameterOptionValues, parameterHelp, infoIconsToRender, inputBounds, granuleTableLimit, beforeCPS, afterCPSL, - afterCPSR, - spatialSearchCollectionConceptId} from '../../constants/rasterParameterConstants'; + afterCPSR} from '../../constants/rasterParameterConstants'; import { Button, Col, Form, OverlayTrigger, Row, Tooltip, Spinner, Alert } from 'react-bootstrap'; import { InfoCircle, Plus, Trash } from 'react-bootstrap-icons'; -import { AdjustType, AdjustValueDecoder, GranuleForTable, GranuleTableProps, InputType, SaveType, SpatialSearchResult, TableTypes, alertMessageInput, allProductParameters, handleSaveResult, validScene } from '../../types/constantTypes'; +import { AdjustType, AdjustValueDecoder, GranuleForTable, GranuleTableProps, InputType, SaveType, SpatialSearchResult, TableTypes, alertMessageInput, allProductParameters, cpsParams, granuleMetadata, handleSaveResult, validScene } from '../../types/constantTypes'; import { addProduct, setSelectedGranules, setGranuleFocus, addGranuleTableAlerts, editProduct, addSpatialSearchResults, clearGranuleTableAlerts, setWaitingForSpatialSearch } from './actions/productSlice'; import { setShowDeleteProductModalTrue } from './actions/modalSlice'; import DeleteGranulesModal from './DeleteGranulesModal'; @@ -55,60 +54,80 @@ const GranuleTable = (props: GranuleTableProps) => { // if any cycle scene and pass parameters in url, add them to table const cyclePassSceneParameters = searchParams.get('cyclePassScene') if (cyclePassSceneParameters) { + setWaitingForScenesToBeAdded(true) const sceneParamArray = Array.from(new Set(cyclePassSceneParameters.split('-'))) sceneParamArray.forEach((sceneParams, index) => { const splitSceneParams = sceneParams.split('_') - handleSave('urlParameter', sceneParamArray.length, index, splitSceneParams[0], splitSceneParams[1], splitSceneParams[2]) + const cpsParams: cpsParams = { + cycleParam: splitSceneParams[0], + passParam: splitSceneParams[1], + sceneParam: splitSceneParams[2] + } + handleSave('urlParameter', sceneParamArray.length, index, [cpsParams]) }) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [tableType === 'granuleSelection' ? null : addedProducts, startTutorial ? searchParams : null]) - const validateCPS = async (cycleToUse: number, passToUse: number, sceneToUse: number[]) => { - 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 getValidationObject = (cycleToUse: number, passToUse: number, sceneToUse: number[], granuleJsonData: SpatialSearchResult[]) => { + const validationObject = {} as validScene + if(granuleJsonData.length !== 0) { + let responseTiles: string[] = [] + let granuleMetadata: granuleMetadata = {} + granuleJsonData.forEach((item: SpatialSearchResult) => { + const {granuleUr, polygons, producerGranuleId, timeEnd, timeStart} = item + const responseTileString = granuleUr.match(`${beforeCPS}([0-9]+(_[0-9]+)+)(${afterCPSR}|${afterCPSL})`)?.[1].split('_').map(item2 => parseInt(item2)).join('_') as string + responseTiles.push(responseTileString) + const polygonToUse = polygons ? getGranuleFootprint(polygons[0]) : [] + const timeStartToUse = new Date(timeStart) + const timeEndToUse = new Date(timeEnd) + granuleMetadata[responseTileString] = {polygons: polygonToUse, producerGranuleId, timeStart: timeStartToUse, timeEnd: timeEndToUse} + }) + + // go through each cycle pass scene combo and see if it is in the return results TODO + sceneToUse.forEach(sceneInput => { + const sceneInputId = `${cycleToUse}_${passToUse}_${sceneInput}` + const validityBool = responseTiles.includes(sceneInputId) + validationObject[sceneInputId] = validityBool ? {'valid': validityBool, polygons: granuleMetadata[sceneInputId].polygons, timeEnd: granuleMetadata[sceneInputId].timeEnd, timeStart: granuleMetadata[sceneInputId].timeStart, producerGranuleId: granuleMetadata[sceneInputId].producerGranuleId} : {valid: false} + }) } - const validationObjectToReturn = await fetch('https://graphql.earthdata.nasa.gov/api', { - method: 'POST', - headers: { - Authorization: `Bearer ${authToken}`, - "Content-Type": "application/json" - }, - body: JSON.stringify({ query: getGranules, variables: getGranuleVariables(cycleToUse, passToUse, sceneToUse)}) - }) - .then(async data => { - const responseJson = await data.json() - let responseTiles: string[] = [] - if(responseJson.data) { - responseTiles = (responseJson.data.tiles.items.map((item: {granuleUr: string}) => item.granuleUr.match(`${beforeCPS}([0-9]+(_[0-9]+)+)(${afterCPSR}|${afterCPSL})`)?.[1].split('_').map(item2 => parseInt(item2)).join('_')) as string[]) - } - const validationObject = {} as validScene + return validationObject + } - // go through each cycle pass scene combo and see if it is in the return results TODO - sceneToUse.forEach(sceneInput => { - const sceneInputId = `${cycleToUse}_${passToUse}_${sceneInput}` - const validityBool = responseTiles.includes(sceneInputId) - validationObject[sceneInputId] = validityBool - }) + const validateCPS = async (cycleToUse: number, passToUse: number, sceneToUse: number[], saveType: SaveType) => { + let validationObjectToReturn = {} + if(saveType === 'spatialSearch') { + // use spatial search data from redux + return getValidationObject(cycleToUse, passToUse, sceneToUse, spatialSearchResults) + } else { + // make calls to get the data + 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'); + } + validationObjectToReturn = await fetch('https://graphql.earthdata.nasa.gov/api', { + method: 'POST', + headers: { + Authorization: `Bearer ${authToken}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ query: getGranules, variables: getGranuleVariables(cycleToUse, passToUse, sceneToUse)}) + }).then(async data => { + const responseJson = await data.json() + return getValidationObject(cycleToUse, passToUse, sceneToUse, responseJson.data.granules.items) + }) + } - return validationObject - }) return validationObjectToReturn } const validateSceneAvailability = async (cycleToUse: number, passToUse: number, sceneToUse: number[], saveType: SaveType): Promise => { try { - if(saveType !== 'spatialSearch') { - return validateCPS(cycleToUse, passToUse, sceneToUse) - } else { - // add all scenes to be valid because spatial search granules don't need to be validated (already in cmr) - return Object.fromEntries(sceneToUse.map(sceneValue => [`${cycleToUse}_${passToUse}_${sceneValue}`, true])) - } + return validateCPS(cycleToUse, passToUse, sceneToUse, saveType) } catch(err) { console.log(err) return {} @@ -127,11 +146,18 @@ const GranuleTable = (props: GranuleTableProps) => { // don't let more than 10 be added scenesFoundArray.push('hit granule limit') } else { - await handleSave('spatialSearch', spatialSearchResults.length, i, spatialSearchResults[i].cycle, spatialSearchResults[i].pass, spatialSearchResults[i].scene).then(result => { - if(result.savedScenes) { - addedScenes.push(...(result.savedScenes).map(productObject => productObject.granuleId)) - } - scenesFoundArray.push(result.result) + const cpsParams: cpsParams = { + cycleParam: spatialSearchResults[i].cycle, + passParam: spatialSearchResults[i].pass, + sceneParam: spatialSearchResults[i].scene + } + await handleSave('spatialSearch', spatialSearchResults.length, i, [cpsParams]).then(results => { + results.forEach(result => { + if(result.savedScenes) { + addedScenes.push(...(result.savedScenes).map(productObject => productObject.granuleId)) + } + scenesFoundArray.push(result.result) + }) }) } } @@ -218,7 +244,6 @@ const GranuleTable = (props: GranuleTableProps) => { const [scene, setScene] = useState(''); const allAddedGranules = addedProducts.map(parameterObject => parameterObject.granuleId) const [waitingForScenesToBeAdded, setWaitingForScenesToBeAdded] = useState(false) - const [waitingForFootprintSearch, setWaitingForFootprintSearch] = useState(false) const getScenesArray = (sceneString: string): string[] => { const scenesArray = [] @@ -294,208 +319,203 @@ const GranuleTable = (props: GranuleTableProps) => { return existingValue } - const getSceneFootprint = async (collectionId: string, granuleId: string) => { - 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 getGranuleFootprint = (polygons: string): LatLngExpression[] => { + const footprintCoordinatesSingleArray = (polygons[0]).split(' ').map((coordinateString: string) => parseFloat(coordinateString)) + let footprintLatLongArray: LatLngExpression[] = [] + for(let i=0; i response.json()).then(data => { - if (data.feed.entry.length > 0) { - const timeStart = new Date(data.feed.entry[0].time_start) - const timeEnd = new Date(data.feed.entry[0].time_end) - const spatialSearchStartDateToUse = new Date(spatialSearchStartDate) - const spatialSearchEndDateToUse = new Date(spatialSearchEndDate) - const granuleInTimeRange: boolean = timeStart > spatialSearchStartDateToUse && timeStart < spatialSearchEndDateToUse && timeEnd > spatialSearchStartDateToUse && timeEnd < spatialSearchEndDateToUse - const footprintCoordinatesSingleArray = (data.feed.entry[0].polygons[0][0]).split(' ').map((coordinateString: string) => parseFloat(coordinateString)) - const granuleFilename = data.feed.entry[0].producer_granule_id - let footprintLatLongArray: LatLngExpression[] = [] - for(let i=0; i { + const spatialSearchStartDateToUse = new Date(spatialSearchStartDate) + const spatialSearchEndDateToUse = new Date(spatialSearchEndDate) + const granuleInTimeRange: boolean = timeStart > spatialSearchStartDateToUse && timeStart < spatialSearchEndDateToUse && timeEnd > spatialSearchStartDateToUse && timeEnd < spatialSearchEndDateToUse + return granuleInTimeRange } - const handleSave = async (saveType: SaveType, totalRuns: number, index: number, cycleParam?: string, passParam?: string, sceneParam?: string): Promise => { + /** + * Handles the save products operation based on the provided parameters. + * + * @param {SaveType} saveType - The type of save operation to perform. + * @param {number} totalRuns - The total number of runs for the save operation. + * @param {number} index - The index of the current run. + * @param {cpsParams[]} [cpsParams] - Optional array of cpsParams (cycle, pass, and scene). + * @return {Promise} A promise that resolves to an array of handleSaveResult objects. + */ + const handleSave = async (saveType: SaveType, totalRuns: number, index: number, cpsParams?: cpsParams[]): 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 = (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) - const validScene = inputIsValid('scene', sceneToUse) - - if (!validCycle || !validPass || !validScene) { - setWaitingForScenesToBeAdded(false) - if (!validCycle) setSaveGranulesAlert('invalidCycle') - if (!validPass) setSaveGranulesAlert('invalidPass') - if (!validScene) setSaveGranulesAlert('invalidScene') - return {result: 'first step'} - } - else { - const granulesToAdd: allProductParameters[] = [] - let someGranulesAlreadyAdded = false - let cyclePassSceneSearchParams = searchParams.get('cyclePassScene') ? String(searchParams.get('cyclePassScene')) : '' - const sceneArray = getScenesArray(sceneToUse) - let validScenesThatCouldNotBeAdded: string[] = [] - // check scenes availability - if(saveType !== 'spatialSearch') { - - } - const validationResult = await validateSceneAvailability(parseInt(cycleToUse), parseInt(passToUse), sceneArray.map(sceneValue => parseInt(sceneValue)), saveType).then(scenesAvailable => { - // return response - setWaitingForScenesToBeAdded(false) - const someScenesNotAvailable = Object.entries(scenesAvailable).some(sceneObjectValidityEntry => { - return !sceneObjectValidityEntry[1] - }) - - const allScenesNotAvailable = Object.entries(scenesAvailable).every(sceneObjectValidityEntry => { - return !sceneObjectValidityEntry[1] - }) - // TODO: make alert more verbose if some granules are added and others are not when adding more than one with scene hyphen - sceneArray.filter(sceneNumber => scenesAvailable[`${cycleToUse}_${passToUse}_${sceneNumber}`]).forEach(async sceneId => { - if ((granulesToAdd.length + addedProducts.length) >= granuleTableLimit) { - validScenesThatCouldNotBeAdded.push(sceneId) - setSaveGranulesAlert('granuleLimit') - } else { - // check if granule exists with that scene, cycle, and pass - const comboAlreadyAdded = alreadyAddedCyclePassScene(cycleToUse, passToUse, sceneId) - const cyclePassSceneInBounds = checkInBounds('cycle', cycleToUse) && checkInBounds('pass', passToUse) && checkInBounds('scene', sceneId) - if (cyclePassSceneInBounds && !comboAlreadyAdded) { - // get the granuleId from it and pass it to the parameters - const parameters: allProductParameters = { - granuleId: `${cycleToUse}_${passToUse}_${sceneId}`, - name: '', - cycle: cycleToUse, - pass: passToUse, - scene: sceneId, - outputGranuleExtentFlag: parameterOptionValues.outputGranuleExtentFlag.default as number, - outputSamplingGridType: parameterOptionValues.outputSamplingGridType.default as string, - rasterResolution: parameterOptionValues.rasterResolutionUTM.default as number, - utmZoneAdjust: parameterOptionValues.utmZoneAdjust.default as string, - mgrsBandAdjust: parameterOptionValues.mgrsBandAdjust.default as string, - footprint: sampleFootprint - } - // add cycle/pass/scene to url parameters - if (!searchParamSceneComboAlreadyInUrl(cyclePassSceneSearchParams, cycleToUse, passToUse, sceneId)) { - cyclePassSceneSearchParams += `${cyclePassSceneSearchParams.length === 0 ? '' : '-'}${cycleToUse}_${passToUse}_${sceneId}` - } - granulesToAdd.push(parameters) - } else if (comboAlreadyAdded) { - someGranulesAlreadyAdded = true - } - } - }) - if (saveType !== 'spatialSearch' && saveType !== 'urlParameter') { - // check if any granules could not be found or they were already added - if (someGranulesAlreadyAdded) { - setSaveGranulesAlert('alreadyAdded') + const cpsParamsIfUndefined = + [{ + cycleParam: cycle, + passParam: pass, + sceneParam: scene, + }] + const cpsParamsToUse = cpsParams ?? cpsParamsIfUndefined + + const getSaveResults = async () => { + const handleSaveResults: handleSaveResult[] = [] + for(let i=0; i 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) + const validScene = inputIsValid('scene', sceneToUse) + + if (!validCycle || !validPass || !validScene) { + setWaitingForScenesToBeAdded(false) + if (!validCycle) setSaveGranulesAlert('invalidCycle') + if (!validPass) setSaveGranulesAlert('invalidPass') + if (!validScene) setSaveGranulesAlert('invalidScene') + handleSaveResults.push({result: 'first step'}) } - if (allScenesNotAvailable) { - setSaveGranulesAlert('allScenesNotAvailable') - } - if (someScenesNotAvailable) { - setSaveGranulesAlert('someScenesNotAvailable') - // set granule alert to show which scenes are missing but also say that you were successful - } - } - return granulesToAdd - }).then(async granulesToAdd => { - if (granulesToAdd.length > 0) { - await Promise.all(granulesToAdd.map(async granule => { - const granuleIdForFootprint = `*${padCPSForCmrQuery(cycleToUse)}_${padCPSForCmrQuery(passToUse)}_${padCPSForCmrQuery(String(Math.floor(parseInt(granule.scene)*2)))}*` - //TODO: change back to spatialSearchCollectionConceptId - return Promise.resolve(await getSceneFootprint(spatialSearchCollectionConceptId as string, granuleIdForFootprint).then(retrievedFootprint => { - - const validFootprintResultArray = retrievedFootprint as (boolean | LatLngExpression[] | string)[] - const footprintResult = validFootprintResultArray[0] - const isInTimeRange = validFootprintResultArray[1] - const granuleFilename = validFootprintResultArray[2] - return {...granule, footprint: footprintResult, inTimeRange: isInTimeRange, fileName: granuleFilename} as allProductParameters - })) - })).then(async productsWithFootprints => { - // don't run time range check if granule was manually entered - if (saveType === 'manual' || saveType === 'urlParameter') { - addSearchParamToCurrentUrlState({'cyclePassScene': cyclePassSceneSearchParams}) - if (saveType !== 'urlParameter' || startTutorial) { - if (validScenesThatCouldNotBeAdded.length > 0) { - setSaveGranulesAlert('someSuccess') + else { + const granulesToAdd: allProductParameters[] = [] + let someGranulesAlreadyAdded = false + let cyclePassSceneSearchParams = searchParams.get('cyclePassScene') ? String(searchParams.get('cyclePassScene')) : '' + const sceneArray = getScenesArray(sceneToUse) + let validScenesThatCouldNotBeAdded: string[] = [] + // check scenes availability + if(saveType !== 'spatialSearch') { + + } + const validationResult = await validateSceneAvailability(parseInt(cycleToUse), parseInt(passToUse), sceneArray.map(sceneValue => parseInt(sceneValue)), saveType).then(sceneValidityResults => { + // return response + setWaitingForScenesToBeAdded(false) + + const sceneValidityList = Object.entries(sceneValidityResults).map(validityObject => validityObject[1].valid) + const someScenesNotAvailable = sceneValidityList.some(sceneObjectValidityEntry => { + return !sceneObjectValidityEntry + }) + + const allScenesNotAvailable = sceneValidityList.every(sceneObjectValidityEntry => { + return !sceneObjectValidityEntry + }) + + // TODO: make alert more verbose if some granules are added and others are not when adding more than one with scene hyphen + sceneArray.filter(sceneNumber => sceneValidityResults[`${cycleToUse}_${passToUse}_${sceneNumber}`].valid).forEach(async sceneId => { + if ((granulesToAdd.length + addedProducts.length) >= granuleTableLimit) { + validScenesThatCouldNotBeAdded.push(sceneId) + setSaveGranulesAlert('granuleLimit') } else { - 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) + // check if granule exists with that scene, cycle, and pass + const comboAlreadyAdded = alreadyAddedCyclePassScene(cycleToUse, passToUse, sceneId) + const cyclePassSceneInBounds = checkInBounds('cycle', cycleToUse) && checkInBounds('pass', passToUse) && checkInBounds('scene', sceneId) + if (cyclePassSceneInBounds && !comboAlreadyAdded) { + const granuleId = `${cycleToUse}_${passToUse}_${sceneId}` + const footprint = sceneValidityResults[granuleId].polygons as LatLngExpression[] + const timeStart = sceneValidityResults[granuleId].timeStart as Date + const timeEnd = sceneValidityResults[granuleId].timeEnd as Date + const producerGranuleId = sceneValidityResults[granuleId].producerGranuleId as string + const utmZone = producerGranuleId.substring(producerGranuleId.indexOf('_UTM') + 4, producerGranuleId.indexOf('_N') - 1) + + // get the granuleId from it and pass it to the parameters + const parameters: allProductParameters = { + granuleId, + name: '', + cycle: cycleToUse, + pass: passToUse, + scene: sceneId, + outputGranuleExtentFlag: parameterOptionValues.outputGranuleExtentFlag.default as number, + outputSamplingGridType: parameterOptionValues.outputSamplingGridType.default as string, + rasterResolution: parameterOptionValues.rasterResolutionUTM.default as number, + utmZoneAdjust: parameterOptionValues.utmZoneAdjust.default as string, + mgrsBandAdjust: parameterOptionValues.mgrsBandAdjust.default as string, + timeStart, + timeEnd, + producerGranuleId, + footprint, + utmZone + } + // add cycle/pass/scene to url parameters + if (!searchParamSceneComboAlreadyInUrl(cyclePassSceneSearchParams, cycleToUse, passToUse, sceneId)) { + cyclePassSceneSearchParams += `${cyclePassSceneSearchParams.length === 0 ? '' : '-'}${cycleToUse}_${passToUse}_${sceneId}` + } + granulesToAdd.push(parameters) + } else if (comboAlreadyAdded) { + someGranulesAlreadyAdded = true + } } }) - if (productsInTimeRange.length > 0) { - setSaveGranulesAlert('success') - dispatch(addProduct(productsInTimeRange)) + if (saveType !== 'spatialSearch' && saveType !== 'urlParameter') { + // check if any granules could not be found or they were already added + if (someGranulesAlreadyAdded) { + setSaveGranulesAlert('alreadyAdded') + } + if (allScenesNotAvailable) { + setSaveGranulesAlert('allScenesNotAvailable') + } + if (someScenesNotAvailable) { + setSaveGranulesAlert('someScenesNotAvailable') + // set granule alert to show which scenes are missing but also say that you were successful + } } - if (productsNotInTimeRange.length > 0) { - // set alerts for not in range - setSaveGranulesAlert('notInTimeRange') + return granulesToAdd + }).then(async granulesToAdd => { + if (granulesToAdd.length > 0) { + // don't run time range check if granule was manually entered + if (saveType === 'manual' || saveType === 'urlParameter') { + addSearchParamToCurrentUrlState({'cyclePassScene': cyclePassSceneSearchParams}) + if (saveType !== 'urlParameter' || startTutorial) { + if (validScenesThatCouldNotBeAdded.length > 0) { + setSaveGranulesAlert('someSuccess') + } else { + setSaveGranulesAlert('success') + } + } + dispatch(addProduct(granulesToAdd)) + } else { + const productsInTimeRange: allProductParameters[] = [] + const productsNotInTimeRange:allProductParameters[] = [] + granulesToAdd.forEach(product => { + const granuleInTimeRangeResult = granuleInTimeRange(product.timeStart, product.timeEnd) + if (granuleInTimeRangeResult){ + delete product.inTimeRange + productsInTimeRange.push(product) + } else if (!granuleInTimeRangeResult) { + 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 {result: 'found something', savedScenes: granulesToAdd} + } else { + if (index+1 === totalRuns){ + return {result: 'noScenesFound'} + } else { + return {result: 'not applicable'} + } } - } - }) - return {result: 'found something', savedScenes: granulesToAdd} - } else { - if (index+1 === totalRuns){ - return {result: 'noScenesFound'} - } else { - return {result: 'not applicable'} + }) + handleSaveResults.push(validationResult) } - } - }) - return validationResult + + } + return handleSaveResults } + + return getSaveResults() + } const handleAllChecked = () => { @@ -760,7 +780,7 @@ const GranuleTable = (props: GranuleTableProps) => { - {waitingForScenesToBeAdded || waitingForSpatialSearch || waitingForFootprintSearch ? + {waitingForScenesToBeAdded || waitingForSpatialSearch ? Loading... : diff --git a/src/constants/graphqlQueries.ts b/src/constants/graphqlQueries.ts index c60eb96..c26dad8 100644 --- a/src/constants/graphqlQueries.ts +++ b/src/constants/graphqlQueries.ts @@ -61,19 +61,37 @@ export const userProductsQuery = ` ` export const getGranules = ` - query($tileParams: GranulesInput) { - tiles: granules(params: $tileParams) { - items { - granuleUr - } +query($params: GranulesInput) { + granules(params: $params) { + items { + producerGranuleId + granuleUr + timeStart + timeEnd + polygons } } +} ` +// export const getSpatialSearchGranules = ` +// query GetSpatialSearchGranules($params: GranulesInput) { +// granules(params: $params) { +// items { +// producerGranuleId +// granuleUr +// timeStart +// timeEnd +// polygons +// } +// } +// } +// ` + export const getGranuleVariables = (cycle: number, pass: number, sceneIds: number[]) => { const sceneIdsForGranuleName = sceneIds.map(sceneId => `SWOT_L2_HR_Raster_*_${padCPSForCmrQuery(String(sceneId))}F_*`) const variables = { - "tileParams": { + "params": { 'collectionConceptIds': [spatialSearchCollectionConceptId], "limit": 100, "cycle": cycle, @@ -87,4 +105,34 @@ export const getGranuleVariables = (cycle: number, pass: number, sceneIds: numbe } } return variables +} + +export const getSpatialSearchGranuleVariables = (polygon: string, collectionConceptId: string, limit: number) => { + const variables = { + "params": { + polygon, + collectionConceptId, + limit + } + } + return variables +} + +export const getFootprintVariables = (cycle: number, pass: number, sceneIds: number[]) => { + const sceneIdsForGranuleName = sceneIds.map(sceneId => `SWOT_L2_HR_Raster_*_${padCPSForCmrQuery(String(sceneId))}F_*`) + const variables = { + "params": { + 'collectionConceptIds': [spatialSearchCollectionConceptId], + "limit": 100, + "cycle": cycle, + "passes": {"0": {"pass": pass}}, + "readableGranuleName": sceneIdsForGranuleName, + "options": { + "readableGranuleName": { + "pattern": true + } + }, + } + } + return variables } \ No newline at end of file diff --git a/src/constants/rasterParameterConstants.ts b/src/constants/rasterParameterConstants.ts index 79b40d9..57a4418 100644 --- a/src/constants/rasterParameterConstants.ts +++ b/src/constants/rasterParameterConstants.ts @@ -1,4 +1,3 @@ -import { LatLngExpression } from "leaflet" import { ParameterHelp, ParameterOptions, granuleAlertMessageConstantType, inputValuesDictionary, parameterValuesDictionary } from "../types/constantTypes" import { FilterParameters } from "../types/historyPageTypes" @@ -215,29 +214,6 @@ export const granuleAlertMessageConstant: granuleAlertMessageConstantType = { mgrsBandAdjust: 'test', } - export const sampleFootprint: LatLngExpression[] = [ - [ - 33.62959926136482, - -119.59722240610449 - ], - [ - 33.93357164098772, - -119.01030070905898 - ], - [ - 33.445222247065175, - -118.6445806486702 - ], - [ - 33.137055033294544, - -119.23445170097719 - ], - [ - 33.629599562267856, - -119.59722227107866 - ] - ] - export const spatialSearchResultLimit = 2000 export const beforeCPS = '_x_x_x_' export const afterCPSR = 'F_' diff --git a/src/types/constantTypes.ts b/src/types/constantTypes.ts index 618dfc0..a44a674 100644 --- a/src/types/constantTypes.ts +++ b/src/types/constantTypes.ts @@ -25,8 +25,11 @@ export interface allProductParameters { utmZoneAdjust: string, mgrsBandAdjust: string, footprint: LatLngExpression[], + producerGranuleId: string, + timeEnd: Date, + timeStart: Date, + utmZone: string, inTimeRange?: boolean, - fileName?: string } export interface GranuleForTable { @@ -134,8 +137,27 @@ export interface newUrlParamsObject { [key: string]: string | number | boolean } +export interface validSceneInfo { + valid: boolean, + polygons?: LatLngExpression[], + timeEnd?: Date, + timeStart?: Date, + producerGranuleId?: string +} + export interface validScene { - [key: string]: boolean + [key: string]: validSceneInfo +} + +export interface granuleMetadataInfo { + polygons: LatLngExpression[], + timeEnd: Date, + timeStart: Date, + producerGranuleId: string +} + +export interface granuleMetadata { + [key: string]: granuleMetadataInfo } export type alertMessageInput = 'success' | 'alreadyAdded' | 'allScenesNotAvailable' | 'alreadyAddedAndNotFound' | 'noScenesAdded' | 'readyForGeneration' | 'invalidCycle' | 'invalidPass' | 'invalidScene' | 'invalidScene' | 'someScenesNotAvailable' | 'granuleLimit' | 'notInTimeRange' | 'noScenesFound' | 'someSuccess' | 'successfullyGenerated' | 'spatialSearchAreaTooLarge' | 'successfullyReGenerated' @@ -143,7 +165,12 @@ export type alertMessageInput = 'success' | 'alreadyAdded' | 'allScenesNotAvaila export interface SpatialSearchResult { cycle: string, pass: string, - scene: string + scene: string, + timeStart: string, + timeEnd: string, + producerGranuleId: string, + granuleUr: string, + polygons: string[] } export type footprintResponse = LatLngExpression[] | boolean @@ -162,4 +189,8 @@ export interface handleSaveResult { // key is the page number and the value is the product ID of the last element on a page export interface RetrievedDataHistory { [key: string]: string +} + +export interface cpsParams { + cycleParam: string, passParam: string, sceneParam: string } \ No newline at end of file From b2531e763c1ad2d4b2fc0e40a837b8c0cc48dba5 Mon Sep 17 00:00:00 2001 From: PODAAC-CICD Date: Thu, 13 Jun 2024 23:32:42 +0000 Subject: [PATCH 40/60] /version v1.0.1-4 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3251e4a..11ccd73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.0.1-3", + "version": "1.0.1-4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.0.1-3", + "version": "1.0.1-4", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index fc3eaf9..d485640 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.0.1-3", + "version": "1.0.1-4", "private": true, "engines": { "node": ">=18.0.0" From 7285557c7447b5c0998b5b5d88ae834fa05051a4 Mon Sep 17 00:00:00 2001 From: Frank Greguska Date: Thu, 13 Jun 2024 17:23:36 -0700 Subject: [PATCH 41/60] Update package lock --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 11ccd73..ef78527 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.0.1-4", + "version": "1.1.0-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.0.1-4", + "version": "1.1.0-0", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", From 252256d4b82f13e404097449c5d934dfdffa5134 Mon Sep 17 00:00:00 2001 From: PODAAC-CICD Date: Fri, 14 Jun 2024 00:25:41 +0000 Subject: [PATCH 42/60] /version v1.1.0-1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ef78527..7d20a72 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.1.0-0", + "version": "1.1.0-1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.1.0-0", + "version": "1.1.0-1", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index 0c94d27..88e2daf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.1.0-0", + "version": "1.1.0-1", "private": true, "engines": { "node": ">=18.0.0" From c32330cd95e40f4cc1169f65355c387a08c8d566 Mon Sep 17 00:00:00 2001 From: Frank Greguska <89428916+frankinspace@users.noreply.github.com> Date: Thu, 13 Jun 2024 17:30:15 -0700 Subject: [PATCH 43/60] Update CHANGE --- CHANGE | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGE b/CHANGE index e4fc398..503f0a4 100644 --- a/CHANGE +++ b/CHANGE @@ -8,3 +8,4 @@ podaac/swodlr-ui deployment podaac/swodlr-ui deployment podaac/swodlr-ui deployment podaac/swodlr-ui deployment +podaac/swodlr-ui deployment From 8e00e2dd5a67b73d4578b75b8ca0fdacbfeca9aa Mon Sep 17 00:00:00 2001 From: Frank Greguska Date: Thu, 13 Jun 2024 17:31:42 -0700 Subject: [PATCH 44/60] /version 1.2.0-0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7d20a72..274e1da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.1.0-1", + "version": "1.2.0-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.1.0-1", + "version": "1.2.0-0", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index 88e2daf..edc9771 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.1.0-1", + "version": "1.2.0-0", "private": true, "engines": { "node": ">=18.0.0" From 732583819f1440f2827ae9d0691c968041d2460c Mon Sep 17 00:00:00 2001 From: PODAAC-CICD Date: Fri, 14 Jun 2024 00:32:24 +0000 Subject: [PATCH 45/60] /version v1.1.0-rc.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7d20a72..4eb5638 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.1.0-1", + "version": "1.1.0-rc.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.1.0-1", + "version": "1.1.0-rc.1", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index 88e2daf..bba36ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.1.0-1", + "version": "1.1.0-rc.1", "private": true, "engines": { "node": ">=18.0.0" From a727df84d62cc695e8a66aa368a4d6d30ae587f2 Mon Sep 17 00:00:00 2001 From: Frank Greguska Date: Thu, 13 Jun 2024 17:36:08 -0700 Subject: [PATCH 46/60] add changelog --- .github/workflows/build.yml | 7 +++++-- CHANGE | 11 ----------- CHANGELOG.md | 24 ++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 13 deletions(-) delete mode 100644 CHANGE create mode 100644 CHANGELOG.md diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 76c717d..d842d02 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,7 +31,7 @@ on: commit: type: string description: Custom commit hash - + env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} @@ -58,11 +58,14 @@ jobs: - name: Initial checkout ${{ github.ref }} if: github.event.inputs.commit == '' uses: actions/checkout@v4 + with: + token: ${{ steps.podaac-cicd.outputs.token }} - name: Adjust to proper commit hash ${{ github.event.inputs.commit }} if: github.event.inputs.commit != '' uses: actions/checkout@v4 with: ref: ${{ github.event.inputs.commit }} + token: ${{ steps.podaac-cicd.outputs.token }} - name: get-npm-version id: package-version uses: martinbeentjes/npm-get-version-action@v1.3.1 @@ -121,7 +124,7 @@ jobs: echo "deploy_env=${{ env.TARGET_ENV }}" >> $GITHUB_OUTPUT VENUE=$(echo "${{ env.TARGET_ENV }}" | tr '[:upper:]' '[:lower:]') echo "deploy_env_lower=$VENUE" >> $GITHUB_OUTPUT - ## Build + ## Build - uses: hashicorp/setup-terraform@v2 with: terraform_version: ${{ env.TERRAFORM_VERSION }} diff --git a/CHANGE b/CHANGE deleted file mode 100644 index 503f0a4..0000000 --- a/CHANGE +++ /dev/null @@ -1,11 +0,0 @@ -podaac/swodlr-ui deployment -podaac/swodlr-ui deployment -podaac/swodlr-ui deployment -podaac/swodlr-ui deployment -podaac/swodlr-ui deployment -podaac/swodlr-ui deployment -podaac/swodlr-ui deployment -podaac/swodlr-ui deployment -podaac/swodlr-ui deployment -podaac/swodlr-ui deployment -podaac/swodlr-ui deployment diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..78c3dcf --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,24 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +## [1.1.0] + +### Added + - Feature/swodlr UI 102 - refactor spatial search validation (#111) + - Feature/swodlr UI sci orbit message (#112) + - Feature/swodlr UI 107 - gray out some product customization options (#110) + - Feature/demo uwg june2024 - bug fixes (#109) + - Feature/swodlr UI 70 - add filtering to my data page and re-generation button (#105) + - Feature/swodlr UI 101 - add file name to granule footprint (#103) + - Issues/swodlr UI 92 - fix tutorial bugs (#99) + - Feature/swodlr UI 71 - add pagination to my data page (#98) + - Feature/swodlr UI cps verification (#100) + + From 0ea155f27e2cbf46b3c8355065ff1ddfe3ce03ec Mon Sep 17 00:00:00 2001 From: PODAAC-CICD Date: Fri, 14 Jun 2024 00:38:21 +0000 Subject: [PATCH 47/60] /version v1.1.0-rc.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4eb5638..0f8b8f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.1.0-rc.1", + "version": "1.1.0-rc.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.1.0-rc.1", + "version": "1.1.0-rc.2", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index bba36ac..1c27a7d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.1.0-rc.1", + "version": "1.1.0-rc.2", "private": true, "engines": { "node": ">=18.0.0" From 4e6a0660ea3caab0d24d9bb58e1a37ac996e7f51 Mon Sep 17 00:00:00 2001 From: PODAAC-CICD Date: Fri, 14 Jun 2024 00:41:59 +0000 Subject: [PATCH 48/60] /version 1.1.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0f8b8f0..e9ce6c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.1.0-rc.2", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.1.0-rc.2", + "version": "1.1.0", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index 1c27a7d..6c4b098 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.1.0-rc.2", + "version": "1.1.0", "private": true, "engines": { "node": ">=18.0.0" From e84f20048ee552f578368e0d47bcc3bac894afd2 Mon Sep 17 00:00:00 2001 From: PODAAC-CICD Date: Fri, 14 Jun 2024 00:48:30 +0000 Subject: [PATCH 49/60] /version v1.2.0-1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 274e1da..4a241c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.2.0-0", + "version": "1.2.0-1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.2.0-0", + "version": "1.2.0-1", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index edc9771..8ccf5e7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.2.0-0", + "version": "1.2.0-1", "private": true, "engines": { "node": ">=18.0.0" From 777319782a2aed53ca1aa6e53d5e2c873d6f6226 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Fri, 21 Jun 2024 11:03:36 -0700 Subject: [PATCH 50/60] Feature/swodlr UI 114 - implement endpoint for My Data page filtering (#116) * feature/swodlr-ui-114: implemented filtering endpoint for some my data page properties * feature/swodlr-ui-114: fixed merge marker * feature/swodlr-ui-114: resolved comments by Josh in PR --------- Co-authored-by: jbyrne --- src/components/history/DataPagination.tsx | 26 ++--- .../history/GeneratedProductHistory.tsx | 66 ++++--------- src/components/history/HistoryFilters.tsx | 96 +++++++++++++++++-- src/components/map/WorldMap.tsx | 1 - .../sidebar/actions/productSlice.ts | 6 ++ src/constants/graphqlQueries.ts | 6 +- src/types/graphqlTypes.ts | 20 +++- src/types/historyPageTypes.ts | 3 +- 8 files changed, 142 insertions(+), 82 deletions(-) diff --git a/src/components/history/DataPagination.tsx b/src/components/history/DataPagination.tsx index 95f8d14..b60b28b 100644 --- a/src/components/history/DataPagination.tsx +++ b/src/components/history/DataPagination.tsx @@ -1,4 +1,4 @@ -import { Col, Pagination, Row, Spinner } from "react-bootstrap"; +import { Col, Pagination, Row } from "react-bootstrap"; import { useAppDispatch, useAppSelector } from "../../redux/hooks"; import { setUserProducts } from "../sidebar/actions/productSlice"; import { productsPerPage } from "../../constants/rasterParameterConstants"; @@ -6,13 +6,12 @@ import { useState } from "react"; const DataPagination = (props: {totalNumberOfProducts: number, totalNumberOfFilteredProducts: number, }) => { - const {totalNumberOfProducts, totalNumberOfFilteredProducts} = props + const { totalNumberOfFilteredProducts} = props const dispatch = useAppDispatch() const userProducts = useAppSelector((state) => state.product.userProducts) const allUserProducts = useAppSelector((state) => state.product.allUserProducts) const [noNextPage, setNoNextPage] = useState(false) const [noPreviousPage, setNoPreviousPage] = useState(true) - const [waitingForPagination, setWaitingForPagination] = useState(false) const [currentPageNumber, setCurrentPageNumber] = useState(1) const numberOfTotalPages = Math.ceil(allUserProducts.length / parseInt(productsPerPage)) @@ -33,16 +32,6 @@ const DataPagination = (props: {totalNumberOfProducts: number, totalNumberOfFilt } } - const waitingForPaginationSpinner = () => { - return ( -
- - Loading... - -
- ) - } - const getPaginationItemsWithEllipsis = () => { let numberOfSlotsFreeLeft = 0 if(currentPageNumber >= numberOfTotalPages-4) { @@ -78,12 +67,12 @@ const DataPagination = (props: {totalNumberOfProducts: number, totalNumberOfFilt } } - if(pagesAllowed[0] > 2) pagesToShow.unshift() - if(pagesAllowed[pagesAllowed.length-1] < numberOfTotalPages-1) pagesToShow.push() + if(pagesAllowed[0] > 2) pagesToShow.unshift() + if(pagesAllowed[pagesAllowed.length-1] < numberOfTotalPages-1) pagesToShow.push() return pagesToShow } - return waitingForPagination ? waitingForPaginationSpinner() : ( + return ( @@ -99,10 +88,9 @@ const DataPagination = (props: {totalNumberOfProducts: number, totalNumberOfFilt : null } -
{totalNumberOfProducts} Total Generated Products
+
{totalNumberOfFilteredProducts} Total Generated Products
) } -export default DataPagination; - +export default DataPagination; \ No newline at end of file diff --git a/src/components/history/GeneratedProductHistory.tsx b/src/components/history/GeneratedProductHistory.tsx index 729617f..afa0cdc 100644 --- a/src/components/history/GeneratedProductHistory.tsx +++ b/src/components/history/GeneratedProductHistory.tsx @@ -1,57 +1,17 @@ import { Alert, Col, OverlayTrigger, Row, Table, Tooltip, Spinner, Form, DropdownButton, Dropdown, Badge } from "react-bootstrap"; import { useAppDispatch, useAppSelector } from "../../redux/hooks"; -import { Product, ProductState } from "../../types/graphqlTypes"; +import { Product } from "../../types/graphqlTypes"; import { useEffect, useState } from "react"; import { InfoCircle } from "react-bootstrap-icons"; import { generatedProductsLabels, infoIconsToRender, parameterHelp, productsPerPage } from "../../constants/rasterParameterConstants"; import { getUserProducts } from "../../user/userData"; import { useLocation, useNavigate } from "react-router-dom"; import DataPagination from "./DataPagination"; -import HistoryFilters from "./HistoryFilters"; -import { Adjust, FilterParameters, OutputGranuleExtentFlagOptions, OutputSamplingGridType, RasterResolution } from "../../types/historyPageTypes"; +import HistoryFilters, { getFilterParameters, productPassesFilterCheck } from "./HistoryFilters"; import { setShowReGenerateProductModalTrue } from "../sidebar/actions/modalSlice"; import ReGenerateProductsModal from "./ReGenerateProductsModal"; -import { setAllUserProducts, setGranulesToReGenerate, setUserProducts, setWaitingForMyDataFiltering, setWaitingForProductsToLoad } from "../sidebar/actions/productSlice"; - -export const productPassesFilterCheck = (currentFilters: FilterParameters, cycle: number, pass: number, scene: number, outputGranuleExtentFlag: boolean, status: string, outputSamplingGridType: string, rasterResolution: number, dateGenerated: string, utmZoneAdjust?: number, mgrsBandAdjust?: number): boolean => { - let productPassesFilter = true - const outputGranuleExtentFlagMap = ['128 x 128','256 x 128'] - - if(currentFilters.cycle !== 'none' && currentFilters.cycle !== String(cycle)) { - productPassesFilter = false - } - if (currentFilters.pass !== 'none' && currentFilters.pass !== String(pass)) { - productPassesFilter = false - } - if (currentFilters.scene !== 'none' && currentFilters.scene !== String(scene)) { - productPassesFilter = false - } - if (currentFilters.outputGranuleExtentFlag.length > 0 && !currentFilters.outputGranuleExtentFlag.includes(outputGranuleExtentFlagMap[+outputGranuleExtentFlag] as OutputGranuleExtentFlagOptions)) { - productPassesFilter = false - } - if (currentFilters.status.length > 0 && !currentFilters.status.includes(status as ProductState)) { - productPassesFilter = false - } - if (currentFilters.outputSamplingGridType.length > 0 && !currentFilters.outputSamplingGridType.includes(outputSamplingGridType as OutputSamplingGridType)) { - productPassesFilter = false - } - if (currentFilters.rasterResolution.length > 0 && !currentFilters.rasterResolution.includes(String(rasterResolution) as RasterResolution)) { - productPassesFilter = false - } - if (utmZoneAdjust !== undefined && currentFilters.utmZoneAdjust.length > 0 && !currentFilters.utmZoneAdjust.includes(String(utmZoneAdjust) as Adjust)) { - productPassesFilter = false - } - if (mgrsBandAdjust !== undefined && currentFilters.mgrsBandAdjust.length > 0 && !currentFilters.mgrsBandAdjust.includes(String(mgrsBandAdjust) as Adjust)) { - productPassesFilter = false - } - if(currentFilters.startDate !== 'none' && new Date(dateGenerated) < currentFilters.startDate) { - productPassesFilter = false - } - if(currentFilters.endDate !== 'none' && new Date(dateGenerated) > currentFilters.endDate) { - productPassesFilter = false - } - return productPassesFilter -} +import { setAllUserProducts, setGranulesToReGenerate, setUserProducts, setWaitingForMyDataFiltering, setWaitingForMyDataFilteringReset, setWaitingForProductsToLoad } from "../sidebar/actions/productSlice"; +import { defaultUserProductsLimit } from "../../constants/graphqlQueries"; const GeneratedProductHistory = () => { const dispatch = useAppDispatch() @@ -60,28 +20,32 @@ const GeneratedProductHistory = () => { const currentFilters = useAppSelector((state) => state.product.currentFilters) const waitingForProductsToLoad = useAppSelector((state) => state.product.waitingForProductsToLoad) const waitingForMyDataFiltering = useAppSelector((state) => state.product.waitingForMyDataFiltering) + const waitingForMyDataFilteringReset = useAppSelector((state) => state.product.waitingForMyDataFilteringReset) const { search } = useLocation() const navigate = useNavigate() const [totalNumberOfProducts, setTotalNumberOfProducts] = useState(0) const [totalNumberOfFilteredProducts, setTotalNumberOfFilteredProducts] = useState(0) const [checkedProducts, setCheckedProducts] = useState([]) const [allChecked, setAllChecked] = useState(false) + const [hasAlreadyLoadedInitialProducts, setHasAlreadyLoadedInitialProducts] = useState(false) useEffect(() => { // get the data for the first page // go through all the user product data to get the id of each one so that const fetchData = async () => { if(!waitingForMyDataFiltering) dispatch(setWaitingForProductsToLoad(true)) - await getUserProducts({limit: '1000000'}).then(response => { + + const productQueryParameters = getFilterParameters(currentFilters, defaultUserProductsLimit) + // add variables for filters + await getUserProducts(productQueryParameters).then(response => { dispatch(setWaitingForProductsToLoad(false)) // filter products for what is in the filter const allProducts = response.products as Product[] setTotalNumberOfProducts(allProducts.length) const filteredProducts = allProducts.filter(product => { - const {status, utmZoneAdjust, mgrsBandAdjust, outputGranuleExtentFlag, outputSamplingGridType, rasterResolution, timestamp: dateGenerated, cycle, pass, scene, granules} = product + const {status, utmZoneAdjust, mgrsBandAdjust, rasterResolution} = product const statusToUse = status[0].state - const outputSamplingGridTypeToUse = outputSamplingGridType === 'GEO' ? 'LAT/LON' : outputSamplingGridType - const productPassesFilter = productPassesFilterCheck(currentFilters, cycle, pass, scene, outputGranuleExtentFlag, statusToUse, outputSamplingGridTypeToUse, rasterResolution, dateGenerated, utmZoneAdjust, mgrsBandAdjust) + const productPassesFilter = productPassesFilterCheck(currentFilters, statusToUse, rasterResolution, utmZoneAdjust, mgrsBandAdjust) if(productPassesFilter) { return product } else { @@ -89,14 +53,16 @@ const GeneratedProductHistory = () => { } }) setTotalNumberOfFilteredProducts(filteredProducts.length) + setHasAlreadyLoadedInitialProducts(true) dispatch(setAllUserProducts(filteredProducts)) const productsPerPageToInt = parseInt(productsPerPage) dispatch(setUserProducts(filteredProducts.slice(0, productsPerPageToInt))) dispatch(setWaitingForMyDataFiltering(false)) + dispatch(setWaitingForMyDataFilteringReset(false)) }) } fetchData().catch(console.error) - }, [currentFilters]); + }, [dispatch, currentFilters, waitingForMyDataFiltering, waitingForMyDataFilteringReset]); // reset all checked checkbox when going to next page useEffect(() => { @@ -226,7 +192,7 @@ const GeneratedProductHistory = () => { {} {!waitingForProductsToLoad && userProducts.length === 0 ? {productHistoryAlert()} : null} - {waitingForProductsToLoad ? waitingForProductsToLoadSpinner() : null} + {waitingForProductsToLoad && !hasAlreadyLoadedInitialProducts ? waitingForProductsToLoadSpinner() : null}
diff --git a/src/components/history/HistoryFilters.tsx b/src/components/history/HistoryFilters.tsx index b876802..b0a048d 100644 --- a/src/components/history/HistoryFilters.tsx +++ b/src/components/history/HistoryFilters.tsx @@ -1,7 +1,7 @@ import { Accordion, Button, Col, Form, Row, Spinner } from "react-bootstrap"; -import { ProductState } from "../../types/graphqlTypes"; +import { GridType, ProductQueryParameters, ProductState, UserProductQueryVariables } from "../../types/graphqlTypes"; import { useAppDispatch, useAppSelector } from "../../redux/hooks"; -import { setCurrentFilter, setWaitingForMyDataFiltering } from "../sidebar/actions/productSlice"; +import { setCurrentFilter, setWaitingForMyDataFiltering, setWaitingForMyDataFilteringReset } from "../sidebar/actions/productSlice"; import { defaultFilterParameters, defaultSpatialSearchEndDate, defaultSpatialSearchStartDate, inputBounds, parameterOptionValues, rasterResolutionOptions } from "../../constants/rasterParameterConstants"; import { useState } from "react"; import { OutputGranuleExtentFlagOptions, OutputSamplingGridType, RasterResolution, Adjust, FilterParameters, FilterAction } from "../../types/historyPageTypes"; @@ -9,6 +9,62 @@ import DatePicker from "react-datepicker"; import "react-datepicker/dist/react-datepicker.css"; import { Formik } from "formik"; +export const getFilterParameters = (currentFilters: FilterParameters, limit?: number, after?: string): UserProductQueryVariables => { + const productVariablesObject: ProductQueryParameters = {} + const {cycle, pass, scene, outputGranuleExtentFlag, outputSamplingGridType, startDate, endDate} = currentFilters + + if(typeof limit !== 'undefined') productVariablesObject.limit = limit + if(typeof after !== 'undefined') productVariablesObject.after = after + if(cycle !== 'none') { + productVariablesObject.cycle = parseInt(cycle) + } + if (pass !== 'none') { + productVariablesObject.pass = parseInt(pass) + } + if (scene !== 'none') { + productVariablesObject.scene = parseInt(scene) + } + if (outputGranuleExtentFlag.length > 0) { + let outputGranuleExtentFlagToUse = null + if(outputGranuleExtentFlag.includes('128 x 128')) outputGranuleExtentFlagToUse = false + if(outputGranuleExtentFlag.includes('256 x 128')) outputGranuleExtentFlagToUse = true + if(outputGranuleExtentFlagToUse !== null) productVariablesObject.outputGranuleExtentFlag = outputGranuleExtentFlagToUse + } + if (outputSamplingGridType.length > 0) { + let outputSamplingGridTypeToUse = null + if(outputSamplingGridType.includes('UTM')) outputSamplingGridTypeToUse = 'UTM' + if(outputSamplingGridType.includes('LAT/LON')) outputSamplingGridTypeToUse = 'GEO' + if(outputSamplingGridTypeToUse !== null) productVariablesObject.outputSamplingGridType = outputSamplingGridTypeToUse as GridType + } + + // filter [status, rasterResolution, utmZoneAdjust, mgrsBandAdjust] after products gotten. + // TODO: Implement these into this function once the filtering endpoint supports it. + if(startDate !== 'none') { + productVariablesObject.afterTimestamp = startDate.toISOString().replace('Z','') + } + if(endDate !== 'none') { + productVariablesObject.beforeTimestamp = endDate.toISOString().replace('Z','') + } + return productVariablesObject as UserProductQueryVariables +} + +export const productPassesFilterCheck = (currentFilters: FilterParameters, status: string, rasterResolution: number, utmZoneAdjust?: number, mgrsBandAdjust?: number): boolean => { + let productPassesFilter = true + + if (currentFilters.status.length > 0 && !currentFilters.status.includes(status as ProductState)) { + return false + } + if (currentFilters.rasterResolution.length > 0 && !currentFilters.rasterResolution.includes(String(rasterResolution) as RasterResolution)) { + return false + } + if (utmZoneAdjust !== undefined && currentFilters.utmZoneAdjust.length > 0 && !currentFilters.utmZoneAdjust.includes(String(utmZoneAdjust) as Adjust)) { + return false + } + if (mgrsBandAdjust !== undefined && currentFilters.mgrsBandAdjust.length > 0 && !currentFilters.mgrsBandAdjust.includes(String(mgrsBandAdjust) as Adjust)) { + return false + } + return productPassesFilter +} const HistoryFilters = () => { const dispatch = useAppDispatch() @@ -16,11 +72,12 @@ const HistoryFilters = () => { const [endDateToUse, setEndDateToUse] = useState(defaultSpatialSearchEndDate) const [startDateToUse, setStartDateToUse] = useState(defaultSpatialSearchStartDate) const waitingForMyDataFiltering = useAppSelector((state) => state.product.waitingForMyDataFiltering) + const waitingForMyDataFilteringReset = useAppSelector((state) => state.product.waitingForMyDataFilteringReset) const [cycleIsValid, setCycleIsValid] = useState(true) const [passIsValid, setPassIsValid] = useState(true) const [sceneIsValid, setSceneIsValid] = useState(true) - const handleChangeFilters = (filter: FilterAction, value: string, valueValidity?: boolean) => { + const handleChangeFilters = (filter: FilterAction, value: string) => { const currentFiltersToModify: FilterParameters = structuredClone(currentFilters) switch(filter) { case 'cycle': @@ -119,6 +176,20 @@ const HistoryFilters = () => { setStartDateToUse(new Date(value)) } break; + case 'reset': + + currentFiltersToModify['cycle'] = 'none' + currentFiltersToModify['pass'] = 'none' + currentFiltersToModify['scene'] = 'none' + currentFiltersToModify['status'] = [] + currentFiltersToModify['outputGranuleExtentFlag'] = [] + currentFiltersToModify['outputSamplingGridType'] = [] + currentFiltersToModify['rasterResolution'] = [] + currentFiltersToModify['utmZoneAdjust'] = [] + currentFiltersToModify['mgrsBandAdjust'] = [] + currentFiltersToModify['endDate'] = 'none' + currentFiltersToModify['startDate'] = 'none' + break; default: } setCurrentFilters(currentFiltersToModify) @@ -129,6 +200,12 @@ const HistoryFilters = () => { dispatch(setWaitingForMyDataFiltering(true)) } + const handleResetFilters = () => { + dispatch(setCurrentFilter(defaultFilterParameters)) + handleChangeFilters('reset', 'random value') + dispatch(setWaitingForMyDataFilteringReset(true)) + } + const statusOptions = ['NEW', 'UNAVAILABLE', 'GENERATING', 'ERROR', 'READY', 'AVAILABLE'] const outputGranuleExtentFlagOptions = ['128 x 128', '256 x 128'] const outputSamplingGridTypeOptions = parameterOptionValues.outputSamplingGridType.values.map(value => { @@ -152,7 +229,7 @@ const HistoryFilters = () => { {}} initialValues={{}}> - handleChangeFilters('cycle', String(e.target.value), !cycleIsInvalid)}/> + handleChangeFilters('cycle', String(e.target.value))}/>
{`Valid Values: ${inputBounds.cycle.min} - ${inputBounds.cycle.max}`}
@@ -166,7 +243,7 @@ const HistoryFilters = () => {
- handleChangeFilters('pass', String(e.target.value), !passIsInvalid)}/> + handleChangeFilters('pass', String(e.target.value))}/>
{`Valid Values: ${inputBounds.pass.min} - ${inputBounds.pass.max}`}
@@ -179,7 +256,7 @@ const HistoryFilters = () => {
- handleChangeFilters('scene', String(e.target.value), !sceneIsInvalid)}/> + handleChangeFilters('scene', String(e.target.value))}/>
{`Valid Values: ${inputBounds.scene.min} - ${inputBounds.scene.max}`}
@@ -319,11 +396,16 @@ const HistoryFilters = () => {
- + {!cycleIsValid || !passIsValid || !sceneIsValid ?
{applyFilterErrorMessage}
: null} ) diff --git a/src/components/map/WorldMap.tsx b/src/components/map/WorldMap.tsx index cb9a9a6..afe3618 100644 --- a/src/components/map/WorldMap.tsx +++ b/src/components/map/WorldMap.tsx @@ -141,7 +141,6 @@ const WorldMap = () => { }) const spatialSearchResults = updatedGranulesToUse.map((updatedGranuleObject: any) => { const {producerGranuleId, granuleUr, cpsString, polygons, timeStart, timeEnd} = updatedGranuleObject - console.log(granuleUr) const cyclePassSceneStringArray = cpsString.split('_').map((id: string) => parseInt(id).toString()) const tileValue = parseInt(cyclePassSceneStringArray?.[2] as string) const sceneToUse = String(Math.floor(tileValue)) diff --git a/src/components/sidebar/actions/productSlice.ts b/src/components/sidebar/actions/productSlice.ts index 068b43a..84bc802 100644 --- a/src/components/sidebar/actions/productSlice.ts +++ b/src/components/sidebar/actions/productSlice.ts @@ -30,6 +30,7 @@ interface GranuleState { currentFilters: FilterParameters, granulesToReGenerate: Product[], waitingForMyDataFiltering: boolean, + waitingForMyDataFilteringReset: boolean, waitingForProductsToLoad: boolean } @@ -60,6 +61,7 @@ const initialState: GranuleState = { currentFilters: defaultFilterParameters, granulesToReGenerate: [], waitingForMyDataFiltering: false, + waitingForMyDataFilteringReset: false, waitingForProductsToLoad: false } @@ -181,6 +183,9 @@ export const productSlice = createSlice({ setWaitingForMyDataFiltering: (state, action: PayloadAction) => { state.waitingForMyDataFiltering = action.payload }, + setWaitingForMyDataFilteringReset: (state, action: PayloadAction) => { + state.waitingForMyDataFilteringReset = action.payload + }, setWaitingForProductsToLoad: (state, action: PayloadAction) => { state.waitingForProductsToLoad = action.payload } @@ -212,6 +217,7 @@ export const { addPageToHistoryPageState, setCurrentFilter, setWaitingForMyDataFiltering, + setWaitingForMyDataFilteringReset, setWaitingForProductsToLoad } = productSlice.actions diff --git a/src/constants/graphqlQueries.ts b/src/constants/graphqlQueries.ts index c26dad8..962c78d 100644 --- a/src/constants/graphqlQueries.ts +++ b/src/constants/graphqlQueries.ts @@ -28,10 +28,10 @@ export const generateL2RasterProductQuery = ` ` export const userProductsQuery = ` - query getUserProducts($limit: Int, $after: ID) + query getUserProducts($limit: Int, $after: ID, $cycle: Int, $pass: Int, $scene: Int, $outputGranuleExtentFlag: Boolean, $outputSamplingGridType: GridType, $beforeTimestamp: String, $afterTimestamp: String) { currentUser { - products (limit: $limit, after: $after) { + products (limit: $limit, after: $after, cycle: $cycle, pass: $pass, scene: $scene, outputGranuleExtentFlag: $outputGranuleExtentFlag, outputSamplingGridType: $outputSamplingGridType, beforeTimestamp: $beforeTimestamp, afterTimestamp: $afterTimestamp) { id timestamp cycle @@ -60,6 +60,8 @@ export const userProductsQuery = ` } ` +export const defaultUserProductsLimit = 1000000 + export const getGranules = ` query($params: GranulesInput) { granules(params: $params) { diff --git a/src/types/graphqlTypes.ts b/src/types/graphqlTypes.ts index 5cc308a..27a4c3b 100644 --- a/src/types/graphqlTypes.ts +++ b/src/types/graphqlTypes.ts @@ -37,6 +37,24 @@ export interface Product { status: Status[] } +export type GridType = 'UTM' | 'GEO' + +export interface ProductQueryParameters { + cycle?: number, + pass?: number, + scene?: number, + outputGranuleExtentFlag?: Boolean, + outputSamplingGridType?: GridType, + rasterResolution?: number, + utmZoneAdjust?: number, + mgrsBandAdjust?: number, + beforeTimestamp?: string, + afterTimestamp?: string, + // pagination + after?: string, + limit?: number +} + export interface CurrentUser { id: string, email: string, @@ -72,7 +90,7 @@ export interface getUserProductsResponse { } export interface UserProductQueryVariables { - [key: string]: string + [key: string]: string | number | GridType } export interface cpsValidationResponse { diff --git a/src/types/historyPageTypes.ts b/src/types/historyPageTypes.ts index 0b51c4d..678e7b0 100644 --- a/src/types/historyPageTypes.ts +++ b/src/types/historyPageTypes.ts @@ -16,7 +16,6 @@ export interface FilterParameters { mgrsBandAdjust: Adjust[], startDate: Date | 'none', endDate: Date | 'none' - } -export type FilterAction = 'cycle' | 'scene' | 'pass' | 'status' | 'outputGranuleExtentFlag' | 'outputSamplingGridType' | 'rasterResolution' | 'utmZoneAdjust' | 'mgrsBandAdjust' | 'startDate' | 'endDate' \ No newline at end of file +export type FilterAction = 'cycle' | 'scene' | 'pass' | 'status' | 'outputGranuleExtentFlag' | 'outputSamplingGridType' | 'rasterResolution' | 'utmZoneAdjust' | 'mgrsBandAdjust' | 'startDate' | 'endDate' | 'reset' \ No newline at end of file From 56e172e00b7ca3e42b493fbb27c2d1676f44181b Mon Sep 17 00:00:00 2001 From: PODAAC-CICD Date: Fri, 21 Jun 2024 18:05:40 +0000 Subject: [PATCH 51/60] /version v1.2.0-2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4a241c7..18dba3c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.2.0-1", + "version": "1.2.0-2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.2.0-1", + "version": "1.2.0-2", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index 8ccf5e7..f4a64ec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.2.0-1", + "version": "1.2.0-2", "private": true, "engines": { "node": ">=18.0.0" From a421db17784f6de75fa970dec764d5f2f01360a2 Mon Sep 17 00:00:00 2001 From: Frank Greguska Date: Fri, 21 Jun 2024 11:35:39 -0700 Subject: [PATCH 52/60] Create patch release for api version fix --- CHANGELOG.md | 23 ++++++++++++++--------- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78c3dcf..5348496 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,17 +8,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +## [1.1.1] + +### Fixed + - UI was getting API version from incorrect venue + ## [1.1.0] ### Added - - Feature/swodlr UI 102 - refactor spatial search validation (#111) - - Feature/swodlr UI sci orbit message (#112) - - Feature/swodlr UI 107 - gray out some product customization options (#110) - - Feature/demo uwg june2024 - bug fixes (#109) - - Feature/swodlr UI 70 - add filtering to my data page and re-generation button (#105) - - Feature/swodlr UI 101 - add file name to granule footprint (#103) - - Issues/swodlr UI 92 - fix tutorial bugs (#99) - - Feature/swodlr UI 71 - add pagination to my data page (#98) - - Feature/swodlr UI cps verification (#100) + - Feature/swodlr UI 102 - refactor spatial search validation (#111) + - Feature/swodlr UI sci orbit message (#112) + - Feature/swodlr UI 107 - gray out some product customization options (#110) + - Feature/demo uwg june2024 - bug fixes (#109) + - Feature/swodlr UI 70 - add filtering to my data page and re-generation button (#105) + - Feature/swodlr UI 101 - add file name to granule footprint (#103) + - Issues/swodlr UI 92 - fix tutorial bugs (#99) + - Feature/swodlr UI 71 - add pagination to my data page (#98) + - Feature/swodlr UI cps verification (#100) diff --git a/package-lock.json b/package-lock.json index 4eb5638..ede59ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.1.0-rc.1", + "version": "1.1.1-rc.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.1.0-rc.1", + "version": "1.1.1-rc.0", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index bba36ac..315fdff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.1.0-rc.1", + "version": "1.1.1-rc.0", "private": true, "engines": { "node": ">=18.0.0" From 0a83beef5c53900160689bded893e2ef8b65e502 Mon Sep 17 00:00:00 2001 From: PODAAC-CICD Date: Fri, 21 Jun 2024 18:37:48 +0000 Subject: [PATCH 53/60] /version v1.1.1-rc.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ede59ce..ab19d17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.1.1-rc.0", + "version": "1.1.1-rc.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.1.1-rc.0", + "version": "1.1.1-rc.1", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index 315fdff..be44a9a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.1.1-rc.0", + "version": "1.1.1-rc.1", "private": true, "engines": { "node": ">=18.0.0" From 8231971be97b016f0289da36cfcebc454dfa977b Mon Sep 17 00:00:00 2001 From: Jimmy Date: Fri, 21 Jun 2024 11:45:11 -0700 Subject: [PATCH 54/60] issue/swodlr-ui-api-version-fix: changes api version to be accurate to the environment (#118) Co-authored-by: jbyrne Co-authored-by: Frank Greguska <89428916+frankinspace@users.noreply.github.com> --- src/components/about/About.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/about/About.tsx b/src/components/about/About.tsx index 83c9a26..5d8a39c 100644 --- a/src/components/about/About.tsx +++ b/src/components/about/About.tsx @@ -11,7 +11,7 @@ const About = () => { const [backendVersion, setBackendVersion] = useState('') useEffect(() => { const fetchData = async () => { - setBackendVersion(await fetch('https://swodlr.podaac.sit.earthdatacloud.nasa.gov/api/about').then((version) => version.json()).then(response => response.version)) + setBackendVersion(await fetch(`${process.env.REACT_APP_SWODLR_API_BASE_URI}/about`).then((version) => version.json()).then(response => response.version)) } fetchData() .catch(console.error); From 2dbe0d6c2bb1d6ad9a027dd2936f843783260d2f Mon Sep 17 00:00:00 2001 From: PODAAC-CICD Date: Fri, 21 Jun 2024 18:47:15 +0000 Subject: [PATCH 55/60] /version v1.1.1-rc.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ab19d17..86b1a76 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.1.1-rc.1", + "version": "1.1.1-rc.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.1.1-rc.1", + "version": "1.1.1-rc.2", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index be44a9a..15bc66d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.1.1-rc.1", + "version": "1.1.1-rc.2", "private": true, "engines": { "node": ">=18.0.0" From 452f99603aa17950858ed464ef1f93e1083a0a6e Mon Sep 17 00:00:00 2001 From: PODAAC-CICD Date: Fri, 21 Jun 2024 19:00:11 +0000 Subject: [PATCH 56/60] /version 1.1.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 86b1a76..8b86f5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.1.1-rc.2", + "version": "1.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.1.1-rc.2", + "version": "1.1.1", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index 15bc66d..7221f44 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.1.1-rc.2", + "version": "1.1.1", "private": true, "engines": { "node": ">=18.0.0" From 4cce54fc71cd07d0dbbabb79a97830d0ead8d328 Mon Sep 17 00:00:00 2001 From: PODAAC-CICD Date: Fri, 21 Jun 2024 19:44:59 +0000 Subject: [PATCH 57/60] /version v1.2.0-3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 18dba3c..fb78c7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.2.0-2", + "version": "1.2.0-3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.2.0-2", + "version": "1.2.0-3", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index f4a64ec..092d286 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.2.0-2", + "version": "1.2.0-3", "private": true, "engines": { "node": ">=18.0.0" From e610e038f3490191c59cb1a211cc00913f3892be Mon Sep 17 00:00:00 2001 From: Frank Greguska Date: Thu, 27 Jun 2024 17:00:09 -0700 Subject: [PATCH 58/60] Prep release --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5348496..31e5526 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +## [1.2.0] + +### Changed + - [issues/114](https://github.com/podaac/swodlr-ui/issues/114): My data page now uses server side filtering for most columns + ## [1.1.1] ### Fixed From 9ede6fbc009113963b11f8ff8e6d0a2613bf8afb Mon Sep 17 00:00:00 2001 From: PODAAC-CICD Date: Fri, 28 Jun 2024 00:02:05 +0000 Subject: [PATCH 59/60] /version v1.2.0-4 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index fb78c7d..0a8e8ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.2.0-3", + "version": "1.2.0-4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.2.0-3", + "version": "1.2.0-4", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index 092d286..459c654 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.2.0-3", + "version": "1.2.0-4", "private": true, "engines": { "node": ">=18.0.0" From 5714d46af329afcc390be5ce2e85f3ddbbdea720 Mon Sep 17 00:00:00 2001 From: Frank Greguska Date: Thu, 27 Jun 2024 17:05:25 -0700 Subject: [PATCH 60/60] /version 1.3.0-0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0a8e8ec..b929561 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swodlr-ui", - "version": "1.2.0-4", + "version": "1.3.0-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "swodlr-ui", - "version": "1.2.0-4", + "version": "1.3.0-0", "dependencies": { "@hexagon/base64": "^1.1.28", "@reduxjs/toolkit": "^1.9.5", diff --git a/package.json b/package.json index 459c654..1155898 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swodlr-ui", - "version": "1.2.0-4", + "version": "1.3.0-0", "private": true, "engines": { "node": ">=18.0.0"