├── .eslintignore
├── .nvmrc
├── static
└── src
│ ├── js
│ ├── util
│ │ ├── bootstrap.js
│ │ ├── polyfill
│ │ │ ├── index.js
│ │ │ └── matches.js
│ │ ├── mocks
│ │ │ └── fileMock.js
│ │ ├── history.js
│ │ ├── map
│ │ │ ├── projections.js
│ │ │ └── actions
│ │ │ │ ├── panBoundsToCenter.js
│ │ │ │ └── panFeatureGroupToCenter.js
│ │ ├── variables.js
│ │ ├── propTypes
│ │ │ └── location.js
│ │ ├── scienceKeywordTypes.js
│ │ ├── isNumber.js
│ │ ├── isBrowserCompatible.js
│ │ ├── getFilenameFromPath.js
│ │ ├── url
│ │ │ ├── stringEncoders.js
│ │ │ ├── integerEncoders.js
│ │ │ ├── arrayEncoders.js
│ │ │ ├── __tests__
│ │ │ │ ├── url.test.js
│ │ │ │ ├── buildAuthenticatedRedirectUrl.test.js
│ │ │ │ ├── focusedGranuleUrl.test.js
│ │ │ │ ├── shapefileId.test.js
│ │ │ │ ├── granuleDataFormatFacetUrl.test.js
│ │ │ │ └── projectFacetUrl.test.js
│ │ │ ├── gridEncoders.js
│ │ │ ├── facetEncoders.js
│ │ │ └── buildAuthenticatedRedirectUrl.js
│ │ ├── facetCategoryAbbreviationsMap.js
│ │ ├── object.js
│ │ ├── request
│ │ │ ├── accessMethodsRequest.js
│ │ │ ├── providerRequest.js
│ │ │ ├── regionRequest.js
│ │ │ ├── preferencesRequest.js
│ │ │ ├── dataQualitySummaryRequest.js
│ │ │ ├── retrievalCollectionRequest.js
│ │ │ ├── shapefileRequest.js
│ │ │ ├── logoutRequest.js
│ │ │ ├── contactInfoRequest.js
│ │ │ ├── loggerRequest.js
│ │ │ ├── admin
│ │ │ │ └── retrievalRequest.js
│ │ │ ├── __tests__
│ │ │ │ └── accessMethodsRequest.test.js
│ │ │ ├── retrievalRequest.js
│ │ │ └── projectRequest.js
│ │ ├── autocompleteFacetsMap.js
│ │ ├── convertRemsToPixels.js
│ │ ├── __tests__
│ │ │ ├── pathStartsWith.test.js
│ │ │ ├── isNumber.test.js
│ │ │ ├── isLinkType.test.js
│ │ │ ├── portals.test.js
│ │ │ ├── limitDecimalPoints.test.js
│ │ │ ├── pluralize.test.js
│ │ │ └── getFilenameFromPath.test.js
│ │ ├── buildPromise.js
│ │ ├── focusedGranule.js
│ │ ├── isLinkType.js
│ │ ├── query.js
│ │ ├── collectionMetadata
│ │ │ ├── granuleLimit.js
│ │ │ ├── scienceKeywords.js
│ │ │ ├── dataCenters.js
│ │ │ ├── doi.js
│ │ │ └── __tests__
│ │ │ │ └── granuleLimit.test.js
│ │ ├── datepicker.js
│ │ ├── regions.js
│ │ ├── pluralize.js
│ │ ├── findProvider.js
│ │ ├── commafy.js
│ │ ├── alphabetic-list.js
│ │ ├── scrollParent.js
│ │ ├── isProjectCollectionValid.js
│ │ ├── getPanelSizeMap.js
│ │ ├── getActivePanelSize.js
│ │ ├── pathStartsWith.js
│ │ ├── limitDecimalPoints.js
│ │ └── itemToRowColumnIndicies.js
│ ├── components
│ │ ├── Map
│ │ │ └── ProjectionSwitcher.scss
│ │ ├── Timeline
│ │ │ └── Timeline.scss
│ │ ├── AdvancedSearchModal
│ │ │ ├── AdvancedSearchModal.scss
│ │ │ ├── AdvancedSearchForm.scss
│ │ │ └── RegionSearch.scss
│ │ ├── Facets
│ │ │ ├── FacetsModal.scss
│ │ │ ├── FacetsSectionHeading.scss
│ │ │ ├── Facets.scss
│ │ │ ├── FacetsList.scss
│ │ │ ├── FacetsSectionHeading.js
│ │ │ └── FacetsModalNav.scss
│ │ ├── Preferences
│ │ │ ├── PreferencesForm.scss
│ │ │ ├── Preferences.scss
│ │ │ └── PreferencesRadioField.scss
│ │ ├── CustomToggle
│ │ │ ├── MoreActionsToggle.scss
│ │ │ └── CustomToggle.scss
│ │ ├── GranuleFilters
│ │ │ ├── GranuleFiltersList.scss
│ │ │ ├── GranuleFiltersActions.scss
│ │ │ ├── GranuleFiltersItem.scss
│ │ │ ├── GranuleFiltersHeader.scss
│ │ │ ├── GranuleFiltersBody.js
│ │ │ ├── GranuleFiltersList.js
│ │ │ └── __tests__
│ │ │ │ └── GranuleFiltersHeader.test.js
│ │ ├── OrderDropdownList
│ │ │ ├── OrderDropdownList.scss
│ │ │ └── OrderDropdownItem.scss
│ │ ├── ButtonDropdown
│ │ │ └── ButtonDropdown.scss
│ │ ├── AdvancedSearchDisplay
│ │ │ ├── AdvancedSearchDisplay.scss
│ │ │ └── AdvancedSearchDisplayEntry.js
│ │ ├── CollapsePanel
│ │ │ └── CollapsePanel.scss
│ │ ├── TemporalDisplay
│ │ │ ├── TemporalDisplay.scss
│ │ │ ├── TemporalSelectionDropdown.scss
│ │ │ ├── TemporalSelectionDropdownMenu.scss
│ │ │ └── TemporalSelectionDropdownToggle.js
│ │ ├── ChunkedOrderModal
│ │ │ └── ChunkedOrderModal.scss
│ │ ├── OrderProgressList
│ │ │ ├── OrderProgressList.scss
│ │ │ └── OrderProgressList.js
│ │ ├── GranuleDetails
│ │ │ ├── skeleton.js
│ │ │ ├── GranuleDetailsMetadata.scss
│ │ │ ├── GranuleDetailsInfo.scss
│ │ │ └── GranuleDetailsHeader.scss
│ │ ├── GranuleResults
│ │ │ ├── GranuleResultsList.scss
│ │ │ ├── GranuleResultsDataLinksButton.scss
│ │ │ └── GranuleResultsBrowseImageCell.scss
│ │ ├── OrderStatus
│ │ │ └── OrderStatusList.scss
│ │ ├── CollectionResults
│ │ │ ├── skeleton.js
│ │ │ ├── CollectionResultsList.scss
│ │ │ ├── CollectionResultsListItem.scss
│ │ │ └── CollectionResultsBody.scss
│ │ ├── CollectionDetails
│ │ │ ├── CollectionDetailsMinimap.scss
│ │ │ ├── skeleton.js
│ │ │ ├── RelatedUrlsModal.scss
│ │ │ ├── CollectionDetailsFeatureGroup.js
│ │ │ └── CollectionDetailsHeader.scss
│ │ ├── Panels
│ │ │ ├── PanelGroupFooter.scss
│ │ │ ├── PanelGroup.scss
│ │ │ ├── PanelSection.scss
│ │ │ ├── PanelGroupHeaderMeta.js
│ │ │ ├── PanelGroupFooter.js
│ │ │ └── PanelSection.js
│ │ ├── TooManyPointsModal
│ │ │ └── TooManyPointsModal.scss
│ │ ├── EDSCAlert
│ │ │ └── EDSCAlert.scss
│ │ ├── SearchPanels
│ │ │ └── SearchPanels.scss
│ │ ├── ProjectPanels
│ │ │ ├── VariableDetailsPanel.scss
│ │ │ └── ProjectPanels.scss
│ │ ├── OverrideTemporalModal
│ │ │ └── OverrideTemporalModal.scss
│ │ ├── EDSCTable
│ │ │ ├── skeleton.js
│ │ │ ├── EDSCTableCell.scss
│ │ │ └── EDSCTableCell.js
│ │ ├── MoreActionsDropdown
│ │ │ └── MoreActionsDropdownItem.scss
│ │ ├── Well
│ │ │ ├── WellMain.js
│ │ │ ├── WellHeading.js
│ │ │ ├── WellFooter.js
│ │ │ ├── WellSection.js
│ │ │ ├── WellIntroduction.js
│ │ │ └── Well.scss
│ │ ├── AdminPage
│ │ │ └── AdminPage.scss
│ │ ├── FilterStack
│ │ │ ├── FilterStack.scss
│ │ │ └── FilterStack.js
│ │ ├── EDSCModal
│ │ │ ├── EDSCModalOverlay.scss
│ │ │ └── EDSCModalOverlay.js
│ │ ├── Dropzone
│ │ │ ├── ShapefileDropzone.scss
│ │ │ └── ShapefileDropzone.js
│ │ ├── ContactInfo
│ │ │ └── ContactInfo.scss
│ │ ├── ProgressRing
│ │ │ └── ProgressRing.scss
│ │ ├── AccessMethod
│ │ │ ├── EchoForm.scss
│ │ │ └── AccessMethod.scss
│ │ ├── SpatialDisplay
│ │ │ ├── SpatialDisplayEntry.js
│ │ │ └── __tests__
│ │ │ │ └── SpatialDisplayEntry.test.js
│ │ ├── DownloadHistory
│ │ │ └── DownloadHistory.scss
│ │ ├── CollectionDownloadDisplay
│ │ │ └── CollectionDownloadDisplay.scss
│ │ ├── SplitBadge
│ │ │ └── SplitBadge.scss
│ │ ├── Sidebar
│ │ │ ├── SidebarSection.scss
│ │ │ ├── Sidebar.scss
│ │ │ └── SidebarSection.js
│ │ ├── ProjectCollections
│ │ │ ├── ProjectCollectionsList.scss
│ │ │ └── ProjectCollections.scss
│ │ ├── EDSCTabs
│ │ │ └── EDSCTabs.scss
│ │ ├── CollectionDetailsHighlights
│ │ │ └── skeleton.js
│ │ ├── FormFields
│ │ │ └── Radio
│ │ │ │ └── Radio.scss
│ │ └── AdminRetrievals
│ │ │ └── __tests__
│ │ │ └── AdminRetrievalForm.test.js
│ ├── actions
│ │ ├── advancedSearch.js
│ │ ├── map.js
│ │ ├── browser.js
│ │ ├── __tests__
│ │ │ └── advancedSearch.test.js
│ │ ├── panels.js
│ │ └── admin
│ │ │ └── isAuthorized.js
│ ├── selectors
│ │ ├── focusedGranule.js
│ │ ├── focusedCollection.js
│ │ ├── __tests__
│ │ │ ├── granuleMetadata.test.js
│ │ │ └── collectionMetadata.test.js
│ │ ├── granuleMetadata.js
│ │ └── collectionMetadata.js
│ ├── reducers
│ │ ├── authToken.js
│ │ ├── providers.js
│ │ ├── portals.js
│ │ ├── admin
│ │ │ ├── isAuthorized.js
│ │ │ └── __tests__
│ │ │ │ └── isAuthorized.test.js
│ │ ├── browser.js
│ │ ├── focusedGranule.js
│ │ ├── dataQualitySummaries.js
│ │ ├── focusedCollection.js
│ │ ├── errors.js
│ │ ├── contactInfo.js
│ │ └── __tests__
│ │ │ ├── authToken.test.js
│ │ │ └── browser.test.js
│ ├── containers
│ │ ├── MapContainer
│ │ │ └── MapContainer.scss
│ │ └── AppLogoContainer
│ │ │ └── AppLogoContainer.js
│ ├── middleware
│ │ └── metrics
│ │ │ └── constants.js
│ └── events
│ │ └── events.js
│ ├── css
│ ├── utils
│ │ ├── mixins
│ │ │ └── mixins.scss
│ │ ├── variables
│ │ │ ├── variables.scss
│ │ │ └── _shadows.scss
│ │ └── utils.scss
│ ├── vendor
│ │ ├── simplebar.scss
│ │ ├── bootstrap
│ │ │ ├── overrides
│ │ │ │ ├── _alert.scss
│ │ │ │ └── _forms.scss
│ │ │ ├── _bootstrap.scss
│ │ │ ├── _utils.scss
│ │ │ ├── _vars.scss
│ │ │ └── _components.scss
│ │ └── datepicker.scss
│ ├── modules
│ │ ├── modules.scss
│ │ ├── _tooltip.scss
│ │ ├── _tables.scss
│ │ ├── _links.scss
│ │ └── _dropdown.scss
│ ├── globalUtils.js
│ ├── main.scss
│ └── _helpers.scss
│ ├── public
│ ├── favicon.ico
│ ├── images
│ │ └── browsers
│ │ │ ├── browser_ie.gif
│ │ │ ├── browser_opera.gif
│ │ │ ├── browser_chrome.gif
│ │ │ ├── browser_firefox.gif
│ │ │ └── browser_safari.gif
│ └── robots.txt
│ ├── assets
│ ├── fonts
│ │ ├── edsc.eot
│ │ ├── edsc.ttf
│ │ └── edsc.woff
│ └── images
│ │ ├── drag.png
│ │ ├── app-logo.png
│ │ ├── app-logo_2x.png
│ │ ├── app-logo_hover.png
│ │ ├── app-logo_hover_2x.png
│ │ ├── blue-bars-circle.png
│ │ ├── projection-sprite.png
│ │ ├── orange-bars-circle.png
│ │ ├── projection-sprite@2x.png
│ │ ├── plate_carree_earth_scaled.png
│ │ └── plate_carree_earth_scaled@2x.png
│ └── partials
│ └── analytics.html
├── .travis
└── travis.enc
├── portals
├── amd
│ ├── images
│ │ └── amd-logo.jpg
│ ├── styles.scss
│ └── config.json
├── idn
│ ├── images
│ │ └── logo-idn.png
│ ├── styles.scss
│ └── config.json
├── cwic
│ ├── images
│ │ └── ceos-logo.png
│ ├── styles.scss
│ └── config.json
├── soos
│ ├── images
│ │ └── soos-logo.png
│ ├── styles.scss
│ └── config.json
├── podaac
│ ├── images
│ │ └── podaac-logo.png
│ ├── styles.scss
│ └── config.json
├── simple
│ └── config.json
├── carve
│ ├── images
│ │ ├── ornl-daac-logo-mono.png
│ │ └── ornl-daac-logo-color.png
│ ├── scripts.js
│ ├── styles.scss
│ └── config.json
├── airmoss
│ ├── images
│ │ ├── ornl-daac-logo-mono.png
│ │ └── ornl-daac-logo-color.png
│ ├── scripts.js
│ ├── styles.scss
│ └── config.json
├── ornldaac
│ ├── images
│ │ ├── ornl-daac-logo-color.png
│ │ └── ornl-daac-logo-mono.png
│ ├── scripts.js
│ ├── styles.scss
│ └── config.json
├── suborbital
│ ├── images
│ │ ├── suborbital-icon-grey-clear.png
│ │ └── suborbital-icon-white-clear.png
│ ├── scripts.js
│ ├── styles.scss
│ └── config.json
├── above
│ └── config.json
├── complex
│ └── config.json
├── standardproducts
│ └── config.json
└── default
│ └── config.json
├── serverless
└── src
│ ├── getRetrieval
│ └── __tests__
│ │ └── mocks.js
│ ├── util
│ ├── echoForms
│ │ ├── namespaces.js
│ │ ├── __tests__
│ │ │ ├── getEmail.test.js
│ │ │ ├── getBoundingBox.test.js
│ │ │ ├── getSwitchFields.test.js
│ │ │ ├── getNameValuePairsForResample.test.js
│ │ │ ├── getNameValuePairsForProjections.test.js
│ │ │ ├── getTopLevelFields.test.js
│ │ │ ├── getSubsetDataLayers.test.js
│ │ │ └── getFieldElementValue.test.js
│ │ ├── getEmail.js
│ │ ├── findFieldElement.js
│ │ ├── getFieldElementValue.js
│ │ ├── getSwitchFields.js
│ │ └── getNameValuePairsForResample.js
│ ├── getUsernameFromToken.js
│ ├── aws
│ │ ├── getSqsConfig.js
│ │ ├── getStepFunctionsConfig.js
│ │ ├── getSecretsManagerConfig.js
│ │ └── getLambdaConfig.js
│ ├── obfuscation
│ │ ├── obfuscateId.js
│ │ └── deobfuscateId.js
│ ├── __tests__
│ │ ├── generateFormDigest.test.js
│ │ ├── getUsernameFromToken.test.js
│ │ ├── getVerifiedJwtToken.test.js
│ │ ├── getJwtToken.test.js
│ │ ├── createJwtToken.test.js
│ │ └── pick.test.js
│ ├── getJwtToken.js
│ ├── generateFormDigest.js
│ ├── cmr
│ │ ├── __tests__
│ │ │ └── prepareExposeHeaders.test.js
│ │ ├── prepareExposeHeaders.js
│ │ └── cmrUrl.js
│ ├── getVerifiedJwtToken.js
│ ├── requestTimeout.js
│ ├── urs
│ │ ├── getEchoToken.js
│ │ ├── getUserAccessToken.js
│ │ └── getAccessTokenFromJwtToken.js
│ ├── database
│ │ └── getDbConnection.js
│ ├── createLimitedShapefile.js
│ ├── createJwtToken.js
│ ├── chunkArray.js
│ └── pick.js
│ ├── submitHarmonyOrder
│ ├── __tests__
│ │ ├── bboxToPolygon.test.js
│ │ └── ccwShapefile.test.js
│ └── bboxToPolygon.js
│ ├── decodeId
│ └── handler.js
│ ├── adminIsAuthorized
│ └── handler.js
│ ├── getAccessMethods
│ ├── supportsVariableSubsetting.js
│ ├── supportsShapefileSubsetting.js
│ └── supportsBoundingBoxSubsetting.js
│ └── relevancyLogger
│ └── handler.js
├── cypress
├── integration
│ ├── retrievals
│ │ └── download_mocks
│ │ │ ├── timeline.json
│ │ │ └── retrievals.json
│ └── utils
│ │ ├── __tests__
│ │ └── getAuthHeaders.test.js
│ │ └── getAuthHeaders.js
├── fixtures
│ └── example.json
├── support
│ ├── getByTestId.js
│ ├── getJwtToken.js
│ └── index.js
└── plugins
│ └── index.js
├── webpack.config.prod.js
├── migrations
├── 1585836600012_clear-site-preferences.js
├── 1562705069972_rename-order-search-params.js
├── 1564488272729_add-environment-to-users.js
├── 1563990176916_alter-retrieval-order-number.js
├── 1565375842170_add-order-information-to-orders.js
├── 1565744336991_add-default-to-order-state.js
├── 1598036919806_add-parent-shapefile-id-selected-features-to-shapefiles.js
├── 1564489093225_remove-environment-default-from-users.js
├── 1568146028532_change-users-not-null.js
├── 1559966487087_projects.js
├── 1556745445952_colormaps.js
└── 1563477325342_access-configurations.js
├── cypress.json
├── .gitignore
├── sharedUtils
├── cmrEnv.js
├── limitedCollectionSize.js
├── prepareGranuleAccessParams.js
├── portalPath.js
├── outputFormatMaps.js
└── prepKeysForCmr.js
├── schemas
└── sitePreferencesUISchema.json
├── font-awesome.config.js
├── bin
├── travis-copy-secrets
├── cypress-prepare-travis
└── ecc-sync
├── .github
└── ISSUE_TEMPLATE
│ ├── custom.md
│ └── feature_request.md
├── serverless-infrastructure.yml
├── babel.config.js
├── jest.config.js
├── postcss.config.js
├── .travis.yml
├── secret.config.json.example
└── static.webpack.config.dev.js
/.eslintignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v12.16.3
2 |
--------------------------------------------------------------------------------
/static/src/js/util/bootstrap.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/src/js/components/Map/ProjectionSwitcher.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/src/css/utils/mixins/mixins.scss:
--------------------------------------------------------------------------------
1 | @import "carat";
2 |
--------------------------------------------------------------------------------
/static/src/js/util/polyfill/index.js:
--------------------------------------------------------------------------------
1 | import './matches'
2 |
--------------------------------------------------------------------------------
/static/src/js/util/mocks/fileMock.js:
--------------------------------------------------------------------------------
1 | module.exports = 'test-file-stub'
2 |
--------------------------------------------------------------------------------
/static/src/css/utils/variables/variables.scss:
--------------------------------------------------------------------------------
1 | @import "colors";
2 | @import "shadows";
3 |
--------------------------------------------------------------------------------
/static/src/css/utils/utils.scss:
--------------------------------------------------------------------------------
1 | @import "variables/variables";
2 | @import "mixins/mixins";
3 |
--------------------------------------------------------------------------------
/static/src/js/components/Timeline/Timeline.scss:
--------------------------------------------------------------------------------
1 | .timeline-tooltip {
2 | color: white;
3 | }
4 |
--------------------------------------------------------------------------------
/.travis/travis.enc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/.travis/travis.enc
--------------------------------------------------------------------------------
/static/src/js/components/AdvancedSearchModal/AdvancedSearchModal.scss:
--------------------------------------------------------------------------------
1 | .advanced-search-modal {
2 | }
3 |
--------------------------------------------------------------------------------
/static/src/css/vendor/simplebar.scss:
--------------------------------------------------------------------------------
1 | /*
2 | * SimpleBar styles
3 | */
4 |
5 | @import "~simplebar/dist/simplebar";
6 |
--------------------------------------------------------------------------------
/static/src/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/static/src/public/favicon.ico
--------------------------------------------------------------------------------
/portals/amd/images/amd-logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/portals/amd/images/amd-logo.jpg
--------------------------------------------------------------------------------
/portals/idn/images/logo-idn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/portals/idn/images/logo-idn.png
--------------------------------------------------------------------------------
/static/src/js/components/Facets/FacetsModal.scss:
--------------------------------------------------------------------------------
1 | .facets-modal {
2 | &__hits {
3 | font-weight: 700;
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/portals/cwic/images/ceos-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/portals/cwic/images/ceos-logo.png
--------------------------------------------------------------------------------
/portals/soos/images/soos-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/portals/soos/images/soos-logo.png
--------------------------------------------------------------------------------
/static/src/assets/fonts/edsc.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/static/src/assets/fonts/edsc.eot
--------------------------------------------------------------------------------
/static/src/assets/fonts/edsc.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/static/src/assets/fonts/edsc.ttf
--------------------------------------------------------------------------------
/static/src/assets/fonts/edsc.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/static/src/assets/fonts/edsc.woff
--------------------------------------------------------------------------------
/static/src/assets/images/drag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/static/src/assets/images/drag.png
--------------------------------------------------------------------------------
/portals/soos/styles.scss:
--------------------------------------------------------------------------------
1 | #SOOS-logo {
2 | height: 40px;
3 | width: 95px;
4 | background-image: url('./images/soos-logo.png');
5 | }
--------------------------------------------------------------------------------
/serverless/src/getRetrieval/__tests__/mocks.js:
--------------------------------------------------------------------------------
1 | export const retrievalPayload = {
2 | pathParameters: {
3 | id: 2
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/static/src/js/components/Preferences/PreferencesForm.scss:
--------------------------------------------------------------------------------
1 | .preferences-form {
2 | &__submit {
3 | width: 5rem;
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/portals/amd/styles.scss:
--------------------------------------------------------------------------------
1 | #amd-logo {
2 | height: 40px;
3 | width: 57px;
4 | background-image: url('./images/amd-logo.jpg');
5 | }
6 |
--------------------------------------------------------------------------------
/portals/cwic/styles.scss:
--------------------------------------------------------------------------------
1 | #ceos-logo {
2 | height: 40px;
3 | width: 95px;
4 | background-image: url('./images/ceos-logo.png');
5 | }
6 |
--------------------------------------------------------------------------------
/portals/idn/styles.scss:
--------------------------------------------------------------------------------
1 | #idn-logo {
2 | height: 40px;
3 | width: 101px;
4 | background-image: url('./images/logo-idn.png');
5 | }
6 |
--------------------------------------------------------------------------------
/portals/podaac/images/podaac-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/portals/podaac/images/podaac-logo.png
--------------------------------------------------------------------------------
/portals/simple/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "parentConfig": "edsc",
3 | "query": {
4 | "echoCollectionId": "C203234523-LAADS"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/static/src/assets/images/app-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/static/src/assets/images/app-logo.png
--------------------------------------------------------------------------------
/portals/podaac/styles.scss:
--------------------------------------------------------------------------------
1 | #podaac-logo {
2 | height: 40px;
3 | width: 40px;
4 | background-image: url('./images/podaac-logo.png');
5 | }
6 |
--------------------------------------------------------------------------------
/static/src/assets/images/app-logo_2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/static/src/assets/images/app-logo_2x.png
--------------------------------------------------------------------------------
/portals/carve/images/ornl-daac-logo-mono.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/portals/carve/images/ornl-daac-logo-mono.png
--------------------------------------------------------------------------------
/static/src/assets/images/app-logo_hover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/static/src/assets/images/app-logo_hover.png
--------------------------------------------------------------------------------
/static/src/js/components/CustomToggle/MoreActionsToggle.scss:
--------------------------------------------------------------------------------
1 | .more-actions-toggle {
2 | color: $color__black--400;
3 | font-size: 1.125rem;
4 | }
5 |
--------------------------------------------------------------------------------
/portals/airmoss/images/ornl-daac-logo-mono.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/portals/airmoss/images/ornl-daac-logo-mono.png
--------------------------------------------------------------------------------
/portals/carve/images/ornl-daac-logo-color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/portals/carve/images/ornl-daac-logo-color.png
--------------------------------------------------------------------------------
/static/src/assets/images/app-logo_hover_2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/static/src/assets/images/app-logo_hover_2x.png
--------------------------------------------------------------------------------
/static/src/assets/images/blue-bars-circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/static/src/assets/images/blue-bars-circle.png
--------------------------------------------------------------------------------
/static/src/assets/images/projection-sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/static/src/assets/images/projection-sprite.png
--------------------------------------------------------------------------------
/static/src/js/components/GranuleFilters/GranuleFiltersList.scss:
--------------------------------------------------------------------------------
1 | .granule-filters-list {
2 | margin: 0;
3 | padding: 0;
4 | list-style: none;
5 | }
6 |
--------------------------------------------------------------------------------
/static/src/js/components/OrderDropdownList/OrderDropdownList.scss:
--------------------------------------------------------------------------------
1 | .order-dropdown-list {
2 | list-style: none;
3 | margin: 0;
4 | padding: 0;
5 | }
6 |
--------------------------------------------------------------------------------
/portals/airmoss/images/ornl-daac-logo-color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/portals/airmoss/images/ornl-daac-logo-color.png
--------------------------------------------------------------------------------
/portals/ornldaac/images/ornl-daac-logo-color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/portals/ornldaac/images/ornl-daac-logo-color.png
--------------------------------------------------------------------------------
/portals/ornldaac/images/ornl-daac-logo-mono.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/portals/ornldaac/images/ornl-daac-logo-mono.png
--------------------------------------------------------------------------------
/static/src/assets/images/orange-bars-circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/static/src/assets/images/orange-bars-circle.png
--------------------------------------------------------------------------------
/static/src/assets/images/projection-sprite@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/static/src/assets/images/projection-sprite@2x.png
--------------------------------------------------------------------------------
/static/src/css/vendor/bootstrap/overrides/_alert.scss:
--------------------------------------------------------------------------------
1 | .alert {
2 | border: 0;
3 |
4 | &-light {
5 | background-color: $color__theme--light;
6 | }
7 | }
--------------------------------------------------------------------------------
/static/src/js/components/GranuleFilters/GranuleFiltersActions.scss:
--------------------------------------------------------------------------------
1 | .granule-filters-actions {
2 | &__action {
3 | margin-right: $spacer/2;
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/static/src/public/images/browsers/browser_ie.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/static/src/public/images/browsers/browser_ie.gif
--------------------------------------------------------------------------------
/cypress/integration/retrievals/download_mocks/timeline.json:
--------------------------------------------------------------------------------
1 | {
2 | "body": [{"concept-id":"C1443528505-LAADS","intervals":[[1515943738,1579132800,113372]]}]
3 | }
4 |
--------------------------------------------------------------------------------
/static/src/js/components/ButtonDropdown/ButtonDropdown.scss:
--------------------------------------------------------------------------------
1 | .button-dropdown {
2 | &__toggle {
3 | &:after {
4 | display: none;
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/static/src/js/components/Facets/FacetsSectionHeading.scss:
--------------------------------------------------------------------------------
1 | .facets-section-heading {
2 | padding: $spacer;
3 | font-size: 1.25rem;
4 | font-weight: 700;
5 | }
6 |
--------------------------------------------------------------------------------
/static/src/public/images/browsers/browser_opera.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/static/src/public/images/browsers/browser_opera.gif
--------------------------------------------------------------------------------
/webpack.config.prod.js:
--------------------------------------------------------------------------------
1 | const ProdStaticWebpackConfig = require('./static.webpack.config.dev.js')
2 |
3 | module.exports = [
4 | ProdStaticWebpackConfig
5 | ]
6 |
--------------------------------------------------------------------------------
/portals/airmoss/scripts.js:
--------------------------------------------------------------------------------
1 | import logo from './images/ornl-daac-logo-color.png'
2 |
3 | // Pre-load logo hover image
4 | const image = new Image()
5 | image.src = logo
6 |
--------------------------------------------------------------------------------
/portals/carve/scripts.js:
--------------------------------------------------------------------------------
1 | import logo from './images/ornl-daac-logo-color.png'
2 |
3 | // Pre-load logo hover image
4 | const image = new Image()
5 | image.src = logo
6 |
--------------------------------------------------------------------------------
/portals/ornldaac/scripts.js:
--------------------------------------------------------------------------------
1 | import logo from './images/ornl-daac-logo-color.png'
2 |
3 | // Pre-load logo hover image
4 | const image = new Image()
5 | image.src = logo
6 |
--------------------------------------------------------------------------------
/static/src/assets/images/plate_carree_earth_scaled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/static/src/assets/images/plate_carree_earth_scaled.png
--------------------------------------------------------------------------------
/static/src/public/images/browsers/browser_chrome.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/static/src/public/images/browsers/browser_chrome.gif
--------------------------------------------------------------------------------
/static/src/public/images/browsers/browser_firefox.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/static/src/public/images/browsers/browser_firefox.gif
--------------------------------------------------------------------------------
/static/src/public/images/browsers/browser_safari.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/static/src/public/images/browsers/browser_safari.gif
--------------------------------------------------------------------------------
/portals/suborbital/images/suborbital-icon-grey-clear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/portals/suborbital/images/suborbital-icon-grey-clear.png
--------------------------------------------------------------------------------
/portals/suborbital/images/suborbital-icon-white-clear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/portals/suborbital/images/suborbital-icon-white-clear.png
--------------------------------------------------------------------------------
/portals/suborbital/scripts.js:
--------------------------------------------------------------------------------
1 | import logo from './images/suborbital-icon-grey-clear.png'
2 |
3 | // Pre-load logo hover image
4 | const image = new Image()
5 | image.src = logo
6 |
--------------------------------------------------------------------------------
/static/src/assets/images/plate_carree_earth_scaled@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilhan-mstf/earthdata-search/master/static/src/assets/images/plate_carree_earth_scaled@2x.png
--------------------------------------------------------------------------------
/migrations/1585836600012_clear-site-preferences.js:
--------------------------------------------------------------------------------
1 | exports.shorthands = undefined
2 |
3 | exports.up = (pgm) => {
4 | pgm.db.query('UPDATE users SET site_preferences=\'{}\';')
5 | }
6 |
--------------------------------------------------------------------------------
/static/src/js/components/AdvancedSearchDisplay/AdvancedSearchDisplay.scss:
--------------------------------------------------------------------------------
1 | .advanced-search-display {
2 | &__text {
3 | font-weight: 300;
4 | font-style: italic;
5 | }
6 | }
7 |
8 |
--------------------------------------------------------------------------------
/static/src/js/components/Facets/Facets.scss:
--------------------------------------------------------------------------------
1 | .facets {
2 | display: block;
3 | width: 100%;
4 | margin: 0;
5 | padding: 0;
6 | list-style: none;
7 | color: $color__white;
8 | }
9 |
--------------------------------------------------------------------------------
/static/src/js/util/history.js:
--------------------------------------------------------------------------------
1 | import { createBrowserHistory } from 'history'
2 |
3 | // Get the browser history
4 | const history = createBrowserHistory()
5 |
6 | export default history
7 |
--------------------------------------------------------------------------------
/static/src/js/util/map/projections.js:
--------------------------------------------------------------------------------
1 | const projections = {
2 | arctic: 'epsg3413',
3 | geographic: 'epsg4326',
4 | antarctic: 'epsg3031'
5 | }
6 |
7 | export default projections
8 |
--------------------------------------------------------------------------------
/static/src/css/modules/modules.scss:
--------------------------------------------------------------------------------
1 | @import 'dropdown';
2 | @import 'edsc-icons';
3 | @import 'error-page';
4 | @import 'links';
5 | @import 'modal';
6 | @import 'tables';
7 | @import 'tooltip';
8 |
--------------------------------------------------------------------------------
/static/src/css/utils/variables/_shadows.scss:
--------------------------------------------------------------------------------
1 | $shadow__default: 0 0 7px rgba(0,0,0,0.1);
2 | $shadow__panel: 0 6px 12px rgba(0,0,0,0.175);
3 | $shadow__panel-dark: 0 6px 12px rgba(0,0,0,0.5);
4 |
--------------------------------------------------------------------------------
/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
--------------------------------------------------------------------------------
/static/src/js/components/CollapsePanel/CollapsePanel.scss:
--------------------------------------------------------------------------------
1 | .collapse-panel {
2 | &__button {
3 | display: flex;
4 | justify-content: space-between;
5 | align-items: center;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/static/src/js/components/TemporalDisplay/TemporalDisplay.scss:
--------------------------------------------------------------------------------
1 | .temporal-display {
2 | color: white;
3 | padding: 10px 15px;
4 | background: rgb(17, 17, 17);
5 | font-size: 1em;
6 | }
7 |
8 |
--------------------------------------------------------------------------------
/static/src/js/components/TemporalDisplay/TemporalSelectionDropdown.scss:
--------------------------------------------------------------------------------
1 | .temporal-selection-dropdown {
2 | display: flex;
3 |
4 | .dropdown-toggle:after {
5 | display: none;
6 | }
7 | }
8 |
9 |
--------------------------------------------------------------------------------
/static/src/css/vendor/datepicker.scss:
--------------------------------------------------------------------------------
1 | /*
2 | * React Datepicker default styles
3 | *
4 | * The default styles supplied for the React Datepicker
5 | */
6 |
7 | @import "~react-datetime/css/react-datetime";
8 |
--------------------------------------------------------------------------------
/static/src/js/components/AdvancedSearchModal/AdvancedSearchForm.scss:
--------------------------------------------------------------------------------
1 | .advanced-search-form {
2 | &__label {
3 | color: $color__black--500;
4 | font-size: 0.925rem;
5 | font-weight: 700;
6 | }
7 | }
--------------------------------------------------------------------------------
/static/src/js/actions/advancedSearch.js:
--------------------------------------------------------------------------------
1 | import { UPDATE_ADVANCED_SEARCH } from '../constants/actionTypes'
2 |
3 | export const updateAdvancedSearch = payload => ({
4 | type: UPDATE_ADVANCED_SEARCH,
5 | payload
6 | })
7 |
--------------------------------------------------------------------------------
/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "nodeVersion": "system",
3 | "baseUrl": "http://localhost:8080",
4 | "viewportWidth": 1400,
5 | "viewportHeight": 900,
6 | "testFiles": "**/*_spec.js",
7 | "fixturesFolder": "cypress/fixtures"
8 | }
9 |
--------------------------------------------------------------------------------
/cypress/support/getByTestId.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Finds an element by using the data-test-id attribute
3 | * @param {String} id data-test-id attribute in markup
4 | */
5 | export const getByTestId = id => cy.get(`[data-test-id=${id}]`)
6 |
--------------------------------------------------------------------------------
/static/src/js/components/ChunkedOrderModal/ChunkedOrderModal.scss:
--------------------------------------------------------------------------------
1 | .chunked-order-modal {
2 | &__body-emphasis {
3 | font-style: italic;
4 | }
5 |
6 | &__body-strong {
7 | font-weight: 500;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/static/src/js/components/OrderProgressList/OrderProgressList.scss:
--------------------------------------------------------------------------------
1 | .order-progress-list {
2 | list-style: none;
3 | overflow: hidden;
4 | overflow-y: scroll;
5 | margin: 0;
6 | margin-top: $spacer;
7 | padding: 0;
8 | }
--------------------------------------------------------------------------------
/static/src/css/globalUtils.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | const resources = [
4 | 'utils/utils.scss',
5 | 'vendor/bootstrap/_vars.scss'
6 | ]
7 |
8 | module.exports = resources.map(file => path.resolve(__dirname, file))
9 |
--------------------------------------------------------------------------------
/static/src/js/components/GranuleDetails/skeleton.js:
--------------------------------------------------------------------------------
1 | export const granuleTitle = [
2 | {
3 | shape: 'rectangle',
4 | left: 0,
5 | top: 0,
6 | height: 22,
7 | width: 280,
8 | radius: 2
9 | }
10 | ]
11 |
--------------------------------------------------------------------------------
/static/src/js/components/GranuleResults/GranuleResultsList.scss:
--------------------------------------------------------------------------------
1 | .granule-results-list {
2 | flex: 1;
3 | width: 100%;
4 |
5 | &__list {
6 | padding: 0 $spacer;
7 | list-style: none;
8 | margin: 0;
9 | }
10 | }
--------------------------------------------------------------------------------
/static/src/js/selectors/focusedGranule.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Retrieve the id of the focused granule from Redux
3 | * @param {Object} state Current state of Redux
4 | */
5 | export const getFocusedGranuleId = state => state.focusedGranule
6 |
--------------------------------------------------------------------------------
/static/src/js/components/CustomToggle/CustomToggle.scss:
--------------------------------------------------------------------------------
1 | .custom-toggle {
2 | &:after {
3 | display: none;
4 | }
5 |
6 | &--icon {
7 | background-color: transparent;
8 | border: 0;
9 | outline: 0;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/static/src/js/components/OrderStatus/OrderStatusList.scss:
--------------------------------------------------------------------------------
1 | .order-status-list {
2 | margin-bottom: $spacer*2;
3 |
4 | &__list {
5 | position: relative;
6 | margin: 0;
7 | padding: 0;
8 | list-style: none;
9 | }
10 | }
--------------------------------------------------------------------------------
/static/src/js/selectors/focusedCollection.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Retrieve the id of the focused collection from Redux
3 | * @param {Object} state Current state of Redux
4 | */
5 | export const getFocusedCollectionId = state => state.focusedCollection
6 |
--------------------------------------------------------------------------------
/static/src/js/util/variables.js:
--------------------------------------------------------------------------------
1 | export const allVariablesSelected = (variableIds, selectedVariableIds) => (
2 | variableIds.every(variableId => selectedVariableIds.indexOf(variableId) > -1)
3 | )
4 |
5 | export default allVariablesSelected
6 |
--------------------------------------------------------------------------------
/static/src/js/components/CollectionResults/skeleton.js:
--------------------------------------------------------------------------------
1 | export const collectionResultsTotal = [
2 | {
3 | shape: 'rectangle',
4 | left: 0,
5 | top: 3,
6 | height: 12,
7 | width: 213,
8 | radius: 2
9 | }
10 | ]
11 |
--------------------------------------------------------------------------------
/serverless/src/util/echoForms/namespaces.js:
--------------------------------------------------------------------------------
1 | // Namespaces used for ECHO Forms
2 | export const namespaces = {
3 | xmlns: 'http://echo.nasa.gov/v9/echoforms',
4 | ecs: 'http://ecs.nasa.gov/options',
5 | info: 'http://eosdis.nasa.gov/esi/info'
6 | }
7 |
--------------------------------------------------------------------------------
/static/src/js/components/GranuleDetails/GranuleDetailsMetadata.scss:
--------------------------------------------------------------------------------
1 | .granule-details-metadata {
2 | &__heading {
3 | font-size: 0.875rem;
4 | font-weight: 300;
5 | }
6 | &__list {
7 | padding: 0;
8 | list-style: none;
9 | }
10 | }
--------------------------------------------------------------------------------
/portals/airmoss/styles.scss:
--------------------------------------------------------------------------------
1 | #ornl-daac-logo {
2 | height: 40px;
3 | width: 53px;
4 | background-image: url('./images/ornl-daac-logo-mono.png');
5 |
6 | &:hover {
7 | background-image: url('./images/ornl-daac-logo-color.png');
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/portals/carve/styles.scss:
--------------------------------------------------------------------------------
1 | #ornl-daac-logo {
2 | height: 40px;
3 | width: 53px;
4 | background-image: url('./images/ornl-daac-logo-mono.png');
5 |
6 | &:hover {
7 | background-image: url('./images/ornl-daac-logo-color.png');
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/portals/ornldaac/styles.scss:
--------------------------------------------------------------------------------
1 | #ornl-daac-logo {
2 | height: 40px;
3 | width: 53px;
4 | background-image: url('./images/ornl-daac-logo-mono.png');
5 |
6 | &:hover {
7 | background-image: url('./images/ornl-daac-logo-color.png');
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Project build files
2 | .DS_Store
3 | .serverless
4 | coverage
5 | dist
6 | node_modules
7 | secret.config.json
8 | tmp
9 |
10 | # VSCode Settings
11 | .vscode
12 |
13 | cypress/screenshots
14 | cypress/videos
15 | overrideStatic.config.json
16 |
--------------------------------------------------------------------------------
/static/src/js/util/propTypes/location.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 |
3 | export const locationPropType = PropTypes.shape({
4 | pathname: PropTypes.string,
5 | search: PropTypes.string,
6 | hash: PropTypes.string,
7 | key: PropTypes.string
8 | })
9 |
--------------------------------------------------------------------------------
/static/src/js/components/CollectionDetails/CollectionDetailsMinimap.scss:
--------------------------------------------------------------------------------
1 | .collection-details-minimap {
2 | &.leaflet-container {
3 | width: 360px;
4 | height: 180px;
5 | }
6 |
7 | .leaflet-interactive {
8 | cursor: default;
9 | }
10 | }
--------------------------------------------------------------------------------
/portals/above/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "org": "ABoVE",
3 | "pageTitle": "ABoVE",
4 | "parentConfig": "edsc",
5 | "query": {
6 | "project": "ABoVE"
7 | },
8 | "ui": {
9 | "showOnlyGranulesCheckbox": false,
10 | "showNonEosdisCheckbox": false
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/portals/suborbital/styles.scss:
--------------------------------------------------------------------------------
1 | #suborbital-logo {
2 | height: 40px;
3 | width: 40px;
4 | background-image: url('./images/suborbital-icon-grey-clear.png');
5 |
6 | &:hover {
7 | background-image: url('./images/suborbital-icon-white-clear.png');
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/static/src/js/actions/map.js:
--------------------------------------------------------------------------------
1 | import { UPDATE_MAP } from '../constants/actionTypes'
2 |
3 | export const updateMap = payload => ({
4 | type: UPDATE_MAP,
5 | payload
6 | })
7 |
8 | export const changeMap = map => (dispatch) => {
9 | dispatch(updateMap(map))
10 | }
11 |
--------------------------------------------------------------------------------
/static/src/js/components/Panels/PanelGroupFooter.scss:
--------------------------------------------------------------------------------
1 | .panel-group-footer {
2 | position: relative;
3 | justify-self: flex-end;
4 | flex-grow: 0;
5 | margin-top: auto;
6 | border-top: 1px solid $border-color;
7 | z-index: 1;
8 | background: $color__white;
9 | }
10 |
--------------------------------------------------------------------------------
/static/src/js/util/scienceKeywordTypes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Listing of the science keyword types
3 | */
4 | export const scienceKeywordTypes = [
5 | 'topic',
6 | 'term',
7 | 'variable_level_1',
8 | 'variable_level_2',
9 | 'variable_level_3',
10 | 'detailed_variable'
11 | ]
12 |
--------------------------------------------------------------------------------
/static/src/js/components/GranuleFilters/GranuleFiltersItem.scss:
--------------------------------------------------------------------------------
1 | .granule-filters-item {
2 | margin-bottom: $spacer*1.25;
3 |
4 | &__heading {
5 | font-size: 1rem;
6 | font-weight: 700;
7 | }
8 |
9 | &__description {
10 | margin-bottom: $spacer/2;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/static/src/js/components/TooManyPointsModal/TooManyPointsModal.scss:
--------------------------------------------------------------------------------
1 | .too-many-points-modal {
2 | &__actions {
3 | display: flex;
4 | justify-content: flex-start;
5 | margin-top: $spacer*2;
6 | }
7 |
8 | &__action {
9 | margin-right: $spacer/2;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/static/src/js/components/CollectionResults/CollectionResultsList.scss:
--------------------------------------------------------------------------------
1 | .collection-results-list {
2 | position: absolute;
3 | top: 0;
4 | bottom: 0;
5 | right: 0;
6 | left: 0;
7 |
8 | &__list {
9 | list-style: none;
10 | margin: 0;
11 | padding: 0;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/static/src/js/components/EDSCAlert/EDSCAlert.scss:
--------------------------------------------------------------------------------
1 | .edsc-alert {
2 | &--small {
3 | font-size: 0.75rem;
4 | }
5 |
6 | &--icon {
7 | display: flex;
8 | align-items: center;
9 | }
10 |
11 | &__icon {
12 | font-size: 0.825rem;
13 | margin-right: 0.75rem;
14 | }
15 | }
--------------------------------------------------------------------------------
/static/src/js/components/Facets/FacetsList.scss:
--------------------------------------------------------------------------------
1 | .facets-list {
2 | margin: 0;
3 | padding: 0;
4 | list-style: none;
5 |
6 | &--light {
7 | .facets-item__label {
8 | &:hover {
9 | background-color: lighten($color__black--200, 5);
10 | }
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/static/src/js/components/CollectionResults/CollectionResultsListItem.scss:
--------------------------------------------------------------------------------
1 | .collection-results-list-item {
2 | &--loading {
3 | display: flex;
4 | align-items: center;
5 | justify-content: center;
6 | padding: $spacer 0;
7 | text-align: center;
8 | color: $color__black--600;
9 | }
10 | }
--------------------------------------------------------------------------------
/static/src/js/util/isNumber.js:
--------------------------------------------------------------------------------
1 | const reg = new RegExp(/^\d+$/)
2 |
3 | /**
4 | * Returns true the string contains only number characters and false if there are any non-number characters
5 | * @return {boolean}
6 | */
7 | export const isNumber = string => reg.test(string)
8 |
9 | export default isNumber
10 |
--------------------------------------------------------------------------------
/cypress/integration/utils/__tests__/getAuthHeaders.test.js:
--------------------------------------------------------------------------------
1 | import { getAuthHeaders } from '../getAuthHeaders'
2 |
3 | describe('getAuthHeaders', () => {
4 | test('builds an object containing standard auth headers', () => {
5 | expect(Object.keys(getAuthHeaders()))
6 | .toEqual(['jwt-token'])
7 | })
8 | })
9 |
--------------------------------------------------------------------------------
/portals/complex/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "logo": {
3 | "image": "http://placehold.it/75x40",
4 | "link": "https://example.com/logo"
5 | },
6 | "pageTitle": "Complex",
7 | "parentConfig": "edsc",
8 | "query": {
9 | "echoCollectionId": "C203234523-LAADS"
10 | },
11 | "title": "Complex"
12 | }
13 |
--------------------------------------------------------------------------------
/static/src/js/actions/browser.js:
--------------------------------------------------------------------------------
1 | import browser from 'browser-detect'
2 |
3 | import { UPDATE_BROWSER_VERSION } from '../constants/actionTypes'
4 |
5 | export const updateBrowserVersion = () => ({
6 | type: UPDATE_BROWSER_VERSION,
7 | payload: browser()
8 | })
9 |
10 | export default updateBrowserVersion
11 |
--------------------------------------------------------------------------------
/static/src/js/util/polyfill/matches.js:
--------------------------------------------------------------------------------
1 | /**
2 | * IE matches polyfill
3 | * https://developer.mozilla.org/en-US/docs/Web/API/Element/matches#Polyfill
4 | */
5 | if (!Element.prototype.matches) {
6 | Element.prototype.matches = Element.prototype.msMatchesSelector
7 | || Element.prototype.webkitMatchesSelector
8 | }
9 |
--------------------------------------------------------------------------------
/sharedUtils/cmrEnv.js:
--------------------------------------------------------------------------------
1 | import { getApplicationConfig } from './config'
2 |
3 | /**
4 | * Return the CMR environment to use
5 | */
6 | export const cmrEnv = () => {
7 | const { env } = getApplicationConfig()
8 |
9 | if (env === 'dev') return 'prod'
10 |
11 | return env
12 | }
13 |
14 | export default cmrEnv
15 |
--------------------------------------------------------------------------------
/static/src/js/util/isBrowserCompatible.js:
--------------------------------------------------------------------------------
1 | import browser from 'browser-detect'
2 |
3 | export const isBrowserCompatible = () => {
4 | const {
5 | name,
6 | versionNumber
7 | } = browser()
8 |
9 | if (name === 'ie' && versionNumber <= 10) {
10 | return false
11 | }
12 |
13 | return true
14 | }
15 |
--------------------------------------------------------------------------------
/migrations/1562705069972_rename-order-search-params.js:
--------------------------------------------------------------------------------
1 | exports.shorthands = undefined
2 |
3 | exports.up = (pgm) => {
4 | pgm.renameColumn('retrieval_orders', 'search_params', 'granule_params')
5 | }
6 |
7 | exports.down = (pgm) => {
8 | pgm.renameColumn('retrieval_orders', 'granule_params', 'search_params')
9 | }
10 |
--------------------------------------------------------------------------------
/static/src/js/components/Preferences/Preferences.scss:
--------------------------------------------------------------------------------
1 | .preferences {
2 | border-radius: 5px;
3 | padding: 30px;
4 | border-color: #e3e3e3;
5 | color: #333333;
6 |
7 | &__loading {
8 | min-height: 10rem;
9 | display: flex;
10 | align-items: center;
11 | justify-content: center;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/static/src/js/components/SearchPanels/SearchPanels.scss:
--------------------------------------------------------------------------------
1 | .search-panels {
2 | &__portal-escape {
3 | color: #424242;
4 | margin: 0;
5 | padding: 0.5rem 1rem;
6 | text-align: center;
7 | font-size: 0.875rem;
8 | }
9 |
10 | &__portal-escape-link {
11 | text-decoration: underline;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/schemas/sitePreferencesUISchema.json:
--------------------------------------------------------------------------------
1 | {
2 | "panelState": {
3 | "ui:widget": "radio",
4 | "ui:field": "radio"
5 | },
6 | "collectionListView": {
7 | "ui:widget": "radio",
8 | "ui:field": "radio"
9 | },
10 | "granuleListView": {
11 | "ui:widget": "radio",
12 | "ui:field": "radio"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/serverless/src/util/getUsernameFromToken.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Strips the username out of the URS token endpoint
3 | * @param {Object} token URS token object
4 | */
5 | export const getUsernameFromToken = (token) => {
6 | const { endpoint } = token
7 | const username = endpoint.split('/').pop()
8 |
9 | return username
10 | }
11 |
--------------------------------------------------------------------------------
/static/src/js/components/AdvancedSearchModal/RegionSearch.scss:
--------------------------------------------------------------------------------
1 | .region-search {
2 | &__selected-region-id {
3 | margin-right: $spacer/2;
4 | font-weight: 700;
5 | }
6 |
7 | &__selected-region-name {
8 | margin-right: $spacer/2;
9 | font-style: italic;
10 | color: $color__black--400;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/static/src/js/components/GranuleDetails/GranuleDetailsInfo.scss:
--------------------------------------------------------------------------------
1 | .granule-details-info {
2 | &__content {
3 | white-space: pre-wrap;
4 | font-family: monospace;
5 | }
6 |
7 | &__spinner {
8 | position: absolute;
9 | top: 50%;
10 | left: 50%;
11 | transform: translate(-50%, -50%);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/static/src/js/components/GranuleFilters/GranuleFiltersHeader.scss:
--------------------------------------------------------------------------------
1 | .granule-filters-header {
2 | &__primary {
3 | margin-left: $spacer/2;
4 | display: inline-block;
5 | font-size: 1rem;
6 | }
7 |
8 | &__secondary {
9 | display: block;
10 | font-size: 1.125rem;
11 | font-weight: 700;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/static/src/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
2 | #
3 | # To ban all spiders from the entire site uncomment the next two lines:
4 | User-Agent: *
5 | Disallow: /downloads
6 | Disallow: /projects
7 | Disallow: /search/granules
8 | Disallow: /contact_info
9 |
--------------------------------------------------------------------------------
/migrations/1564488272729_add-environment-to-users.js:
--------------------------------------------------------------------------------
1 | exports.shorthands = undefined
2 |
3 | exports.up = (pgm) => {
4 | pgm.addColumns('users', {
5 | environment: { type: 'varchar(1000)', notNull: true, default: 'prod' }
6 | })
7 | }
8 |
9 | exports.down = (pgm) => {
10 | pgm.dropColumns('users', ['environment'])
11 | }
12 |
--------------------------------------------------------------------------------
/serverless/src/util/aws/getSqsConfig.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns an environment specific configuration object for SQS
3 | * @return {Object} A configuration object for SQS
4 | */
5 | export const getSqsConfig = () => {
6 | const productionConfig = {
7 | apiVersion: '2012-11-05'
8 | }
9 |
10 | return productionConfig
11 | }
12 |
--------------------------------------------------------------------------------
/static/src/css/modules/_tooltip.scss:
--------------------------------------------------------------------------------
1 | .tooltip {
2 | font-size: .75rem;
3 |
4 | &--large {
5 | font-size: 0.625rem;
6 | }
7 |
8 | &--ta-left {
9 | .tooltip-inner {
10 | text-align: left;
11 | }
12 | }
13 |
14 | &--wide {
15 | .tooltip-inner {
16 | max-width: 400px;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/static/src/js/components/GranuleResults/GranuleResultsDataLinksButton.scss:
--------------------------------------------------------------------------------
1 | .granule-results-data-links-button {
2 | &__dropdown-item {
3 | color: $color__black--800;
4 | font-size: 0.875rem;
5 |
6 | &:hover {
7 | background-color: $color__blue--dark;
8 | color: $color__black--200;
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/serverless/src/util/obfuscation/obfuscateId.js:
--------------------------------------------------------------------------------
1 | import ScatterSwap from 'scatter-swap'
2 |
3 | /**
4 | * Obfuscate a database ID to hide its identity
5 | * @param {Integer} value The value to be obfuscated
6 | */
7 | export const obfuscateId = (value, spin = process.env.obfuscationSpin) => (
8 | new ScatterSwap(value, spin).hash()
9 | )
10 |
--------------------------------------------------------------------------------
/static/src/css/vendor/bootstrap/_bootstrap.scss:
--------------------------------------------------------------------------------
1 | /*
2 | * Bootstrap utils
3 | */
4 |
5 | @import "utils";
6 |
7 | /*
8 | * Bootstrap components
9 | */
10 |
11 | @import "components";
12 |
13 | /*
14 | * Bootstrap overrides
15 | */
16 |
17 | @import "overrides/alert";
18 | @import "overrides/dropdown";
19 | @import "overrides/forms";
20 |
--------------------------------------------------------------------------------
/static/src/js/util/getFilenameFromPath.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Given a filepath, returns the filename and extension
3 | * @param {String} path A filepath
4 | * @returns {String} A filename with extension
5 | */
6 | export const getFilenameFromPath = (path) => {
7 | if (!path) return ''
8 | return path.substring(path.lastIndexOf('/') + 1)
9 | }
10 |
--------------------------------------------------------------------------------
/static/src/css/modules/_tables.scss:
--------------------------------------------------------------------------------
1 | .table {
2 | thead {
3 | th {
4 | font-size: 0.825rem;
5 | color: $color__black--500;
6 | border-bottom: 0;
7 | border-top: 0;
8 | }
9 | }
10 |
11 | td {
12 | vertical-align: middle;
13 | }
14 |
15 | td,
16 | th {
17 | padding: .625rem 1rem;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/static/src/js/components/GranuleDetails/GranuleDetailsHeader.scss:
--------------------------------------------------------------------------------
1 | .granule-details-header {
2 | &__primary {
3 | padding: 0.5rem 1rem;
4 | }
5 |
6 | &__title {
7 | display: inline;
8 | margin-right: 0.75rem;
9 | font-weight: 700;
10 | font-size: 1rem;
11 | vertical-align: middle;
12 | word-break: break-all;
13 | }
14 | }
--------------------------------------------------------------------------------
/static/src/js/components/ProjectPanels/VariableDetailsPanel.scss:
--------------------------------------------------------------------------------
1 | .variable-details-panel {
2 | &__heading {
3 | margin: 0;
4 | font-size: 1.5rem;
5 | font-weight: 700;
6 | }
7 |
8 | &__longname {
9 | margin-bottom: $spacer;
10 | font-style: italic;
11 | color: $color__black--500;
12 | font-weight: 400;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/cypress/integration/utils/getAuthHeaders.js:
--------------------------------------------------------------------------------
1 | import { getEnvironmentConfig } from '../../../sharedUtils/config'
2 |
3 | /**
4 | * Returns common authentication headers for integration tests
5 | */
6 | export const getAuthHeaders = () => {
7 | const { jwtToken } = getEnvironmentConfig('test')
8 | return {
9 | 'jwt-token': jwtToken
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/migrations/1563990176916_alter-retrieval-order-number.js:
--------------------------------------------------------------------------------
1 | exports.shorthands = undefined
2 |
3 | exports.up = (pgm) => {
4 | pgm.alterColumn('retrieval_orders', 'order_number', {
5 | type: 'varchar(1000)'
6 | })
7 | }
8 |
9 | exports.down = (pgm) => {
10 | pgm.alterColumn('retrieval_orders', 'order_number', {
11 | type: 'integer'
12 | })
13 | }
14 |
--------------------------------------------------------------------------------
/static/src/js/components/Preferences/PreferencesRadioField.scss:
--------------------------------------------------------------------------------
1 | .preferences-radio-field {
2 | &__radio {
3 | border: none;
4 | padding: none;
5 | }
6 |
7 | &__name {
8 | font-weight: bold;
9 | }
10 |
11 | &__description {
12 | margin-bottom: $spacer/2;
13 | color: $color__black--400;
14 | font-size: 0.875rem;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/static/src/js/util/url/stringEncoders.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Decodes a string parameter (returns the same value)
3 | * @param {string} string
4 | */
5 | export const decodeString = string => string
6 |
7 |
8 | /**
9 | * Encodes a string parameter (returns the same value)
10 | * @param {string} string
11 | */
12 | export const encodeString = string => string || ''
13 |
--------------------------------------------------------------------------------
/portals/standardproducts/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "org": "Standard Products",
3 | "pageTitle": "Standard Products",
4 | "parentConfig": "edsc",
5 | "query": {
6 | "tagKey": "gov.nasa.eosdis.standardproduct",
7 | "hasGranulesOrCwic": null
8 | },
9 | "ui": {
10 | "showOnlyGranulesCheckbox": false,
11 | "showNonEosdisCheckbox": false
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/static/src/css/main.scss:
--------------------------------------------------------------------------------
1 | /*
2 | * Vendor styles
3 | */
4 | @import 'vendor/bootstrap/bootstrap';
5 | @import 'vendor/datepicker';
6 | @import 'vendor/leaflet';
7 | @import 'vendor/simplebar';
8 |
9 | /*
10 | * Application styles
11 | */
12 | @import 'utils/utils';
13 | @import 'base';
14 | @import 'helpers';
15 | @import 'layout';
16 | @import 'modules/modules';
17 |
--------------------------------------------------------------------------------
/serverless/src/util/aws/getStepFunctionsConfig.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns an environment specific configuration object for Step Functions
3 | * @return {Object} A configuration object for Step Functions
4 | */
5 | export const getStepFunctionsConfig = () => {
6 | const productionConfig = {
7 | apiVersion: '2016-11-23'
8 | }
9 |
10 | return productionConfig
11 | }
12 |
--------------------------------------------------------------------------------
/serverless/src/util/aws/getSecretsManagerConfig.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns an environment specific configuration object for Secrets Manager
3 | * @return {Object} A configuration object for Secrets Manager
4 | */
5 | export const getSecretsManagerConfig = () => {
6 | const productionConfig = {
7 | apiVersion: '2017-10-17'
8 | }
9 |
10 | return productionConfig
11 | }
12 |
--------------------------------------------------------------------------------
/serverless/src/util/obfuscation/deobfuscateId.js:
--------------------------------------------------------------------------------
1 | import ScatterSwap from 'scatter-swap'
2 |
3 | /**
4 | * Deobfuscate a database ID determine it's original value
5 | * @param {Integer} value The value to deobfuscate
6 | */
7 | export const deobfuscateId = (value, spin = process.env.obfuscationSpin) => (
8 | parseInt(new ScatterSwap(value, spin).reverseHash(), 10)
9 | )
10 |
--------------------------------------------------------------------------------
/static/src/js/components/CollectionDetails/skeleton.js:
--------------------------------------------------------------------------------
1 | export const collectionTitleSkeleton = [
2 | {
3 | shape: 'rectangle',
4 | left: 0,
5 | top: 0,
6 | height: 22,
7 | width: 280,
8 | radius: 2
9 | },
10 | {
11 | shape: 'rectangle',
12 | left: 290,
13 | top: 2,
14 | height: 18,
15 | width: 65,
16 | radius: 2
17 | }
18 | ]
19 |
--------------------------------------------------------------------------------
/serverless/src/submitHarmonyOrder/__tests__/bboxToPolygon.test.js:
--------------------------------------------------------------------------------
1 | import { bboxToPolygon } from '../bboxToPolygon'
2 |
3 | describe('bboxToPolygon', () => {
4 | test('returns a CW polygon', () => {
5 | expect(bboxToPolygon([[5, 0], [15, 10]])).toEqual([[
6 | [15, 10],
7 | [5, 10],
8 | [5, 0],
9 | [15, 0],
10 | [15, 10]
11 | ]])
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/serverless/src/util/__tests__/generateFormDigest.test.js:
--------------------------------------------------------------------------------
1 | import { generateFormDigest } from '../generateFormDigest'
2 |
3 | describe('generateFormDigest', () => {
4 | test('retrieves the username from the endpoint field', () => {
5 | const form = 'mock echo form data'
6 |
7 | expect(generateFormDigest(form)).toEqual('e60e33c401915211e731cbcf993ecfe1102a4a22')
8 | })
9 | })
10 |
--------------------------------------------------------------------------------
/static/src/js/reducers/authToken.js:
--------------------------------------------------------------------------------
1 | import { UPDATE_AUTH } from '../constants/actionTypes'
2 |
3 | const initialState = ''
4 |
5 | const authTokenReducer = (state = initialState, action) => {
6 | switch (action.type) {
7 | case UPDATE_AUTH: {
8 | return action.payload
9 | }
10 | default:
11 | return state
12 | }
13 | }
14 |
15 | export default authTokenReducer
16 |
--------------------------------------------------------------------------------
/serverless/src/util/getJwtToken.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns the JWT Token from our custom authorizer context
3 | * @param {Object} event Details about the HTTP request that it received
4 | */
5 | export const getJwtToken = (event) => {
6 | const { requestContext = {} } = event
7 | const { authorizer = {} } = requestContext
8 | const { jwtToken } = authorizer
9 |
10 | return jwtToken
11 | }
12 |
--------------------------------------------------------------------------------
/static/src/js/components/OverrideTemporalModal/OverrideTemporalModal.scss:
--------------------------------------------------------------------------------
1 | .choice {
2 | width: 50%;
3 | text-align: center;
4 | padding: 0.5em 0;
5 |
6 | button {
7 | display: inline-block;
8 | }
9 | }
10 |
11 | .choice-1 {
12 | float: left;
13 | }
14 |
15 | .choice-2 {
16 | float: right;
17 | }
18 |
19 | .override-temporal-modal__footer-actions {
20 | width: 100%;
21 | }
22 |
--------------------------------------------------------------------------------
/font-awesome.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | styleLoader: 'style-loader!css-loader!sass-loader',
3 | styles: {
4 | mixins: true,
5 | 'bordered-pulled': true,
6 | core: true,
7 | 'fixed-width': true,
8 | icons: true,
9 | larger: true,
10 | list: true,
11 | path: true,
12 | 'rotated-flipped': true,
13 | animated: true,
14 | stacked: true
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/serverless/src/util/__tests__/getUsernameFromToken.test.js:
--------------------------------------------------------------------------------
1 | import { getUsernameFromToken } from '../getUsernameFromToken'
2 |
3 | describe('getUsernameFromToken', () => {
4 | test('retrieves the username from the endpoint field', () => {
5 | const token = {
6 | endpoint: '/api/users/testuser'
7 | }
8 |
9 | expect(getUsernameFromToken(token)).toEqual('testuser')
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/static/src/css/_helpers.scss:
--------------------------------------------------------------------------------
1 | .visually-hidden {
2 | position: absolute !important;
3 | height: 1px; width: 1px;
4 | overflow: hidden;
5 | clip: rect(1px 1px 1px 1px);
6 | clip: rect(1px, 1px, 1px, 1px);
7 | }
8 |
9 | .visually-hidden a:focus,
10 | .visually-hidden input:focus,
11 | .visually-hidden button:focus {
12 | position:static;
13 | width:auto; height:auto;
14 | }
15 |
--------------------------------------------------------------------------------
/static/src/js/util/facetCategoryAbbreviationsMap.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Maps the CMR Autocomplete types to their abbriviations
3 | */
4 | export const facetCategoryAbbreviationsMap = {
5 | organization: 'Org.',
6 | instrument: 'Inst.',
7 | granule_data_format: 'Gran. Fmt.',
8 | platform: 'Plat.',
9 | processing_level_id: 'Proc. Lvl.',
10 | project: 'Proj.',
11 | science_keywords: 'Kwd.'
12 | }
13 |
--------------------------------------------------------------------------------
/static/src/js/util/object.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Lookup a object key given a value
3 | * @param {string} object JavaScript Object with key-value pairs
4 | * @param {string} value A value in the object
5 | * @return {string} A key in the object
6 | */
7 | const getObjectKeyByValue = (object, value) => Object.keys(object)
8 | .find(key => object[key] === value)
9 |
10 | export default getObjectKeyByValue
11 |
--------------------------------------------------------------------------------
/migrations/1565375842170_add-order-information-to-orders.js:
--------------------------------------------------------------------------------
1 | exports.shorthands = undefined
2 |
3 | exports.up = (pgm) => {
4 | pgm.addColumns('retrieval_orders', {
5 | order_information: {
6 | type: 'jsonb',
7 | notNull: true,
8 | default: '{}'
9 | }
10 | })
11 | }
12 |
13 | exports.down = (pgm) => {
14 | pgm.dropColumns('retrieval_orders', ['order_information'])
15 | }
16 |
--------------------------------------------------------------------------------
/static/src/css/vendor/bootstrap/overrides/_forms.scss:
--------------------------------------------------------------------------------
1 | .form-control-basic {
2 | width: auto;
3 | height: auto;
4 | margin: 0;
5 | padding: 0;
6 | border: none;
7 | border-radius: 0;
8 | color: inherit;
9 | background-color: transparent;
10 | background-clip: initial;
11 |
12 | &.is-invalid {
13 | padding: 0;
14 | background-image: none;
15 | border: none;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/static/src/js/reducers/providers.js:
--------------------------------------------------------------------------------
1 | import {
2 | SET_PROVIDERS
3 | } from '../constants/actionTypes'
4 |
5 | const initialState = []
6 |
7 | const providersReducer = (state = initialState, action) => {
8 | switch (action.type) {
9 | case SET_PROVIDERS: {
10 | return action.payload
11 | }
12 | default:
13 | return state
14 | }
15 | }
16 |
17 | export default providersReducer
18 |
--------------------------------------------------------------------------------
/static/src/js/components/EDSCTable/skeleton.js:
--------------------------------------------------------------------------------
1 | export const rowContentLarge = [
2 | {
3 | shape: 'rectangle',
4 | left: 0,
5 | top: 3,
6 | height: 12,
7 | width: 213,
8 | radius: 2
9 | }
10 | ]
11 |
12 | export const rowContentSmall = [
13 | {
14 | shape: 'rectangle',
15 | left: 0,
16 | top: 3,
17 | height: 12,
18 | width: 80,
19 | radius: 2
20 | }
21 | ]
22 |
--------------------------------------------------------------------------------
/static/src/js/reducers/portals.js:
--------------------------------------------------------------------------------
1 | import { ADD_PORTAL } from '../constants/actionTypes'
2 |
3 | const initialState = {
4 | portalId: ''
5 | }
6 |
7 | const portalsReducer = (state = initialState, action) => {
8 | switch (action.type) {
9 | case ADD_PORTAL: {
10 | return action.payload
11 | }
12 | default:
13 | return state
14 | }
15 | }
16 |
17 | export default portalsReducer
18 |
--------------------------------------------------------------------------------
/bin/travis-copy-secrets:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cp secret.config.json.example secret.config.json
4 |
5 | # read in static.config.json
6 | config="`cat static.config.json`"
7 |
8 | # update keys for deployment
9 | config="`jq '.application.ciMode = $newValue' --arg newValue true <<< $config`"
10 |
11 | # overwrite static.config.json with new values
12 | echo $config > tmp.$$.json && mv tmp.$$.json static.config.json
13 |
--------------------------------------------------------------------------------
/static/src/js/components/MoreActionsDropdown/MoreActionsDropdownItem.scss:
--------------------------------------------------------------------------------
1 | .more-actions-dropdown-item {
2 | padding: $spacer/4 $spacer/2;
3 | font-size: 0.75rem;
4 |
5 | &__icon {
6 | width: 1rem;
7 | margin-right: $spacer/4;
8 | text-align: center;
9 | color: $color__black--300;
10 |
11 | .more-actions-dropdown-item:hover & {
12 | color: $color__black--400;
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/static/src/js/components/Well/WellMain.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | export const WellMain = ({
5 | children
6 | }) => (
7 |
8 | {children}
9 |
10 | )
11 |
12 | WellMain.defaultProps = {
13 | children: null
14 | }
15 |
16 | WellMain.propTypes = {
17 | children: PropTypes.node
18 | }
19 |
20 | export default WellMain
21 |
--------------------------------------------------------------------------------
/static/src/js/components/AdminPage/AdminPage.scss:
--------------------------------------------------------------------------------
1 | .admin-page {
2 | &__header {
3 | margin-bottom: 1.25rem;
4 | border-bottom: 1px solid $color__black--200;
5 | }
6 |
7 | &__breadcrumbs {
8 | background: none;
9 | padding: 0;
10 | padding-bottom: 0.5rem;
11 | margin-bottom: 1.5rem;
12 | }
13 |
14 | &__page-title {
15 | font-size: 1.5em;
16 | margin-bottom: 0.5rem;
17 | }
18 | }
--------------------------------------------------------------------------------
/static/src/js/components/Well/WellHeading.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | export const WellSection = ({ children }) => (
5 |
6 | {children}
7 |
8 | )
9 |
10 | WellSection.defaultProps = {
11 | children: null
12 | }
13 |
14 | WellSection.propTypes = {
15 | children: PropTypes.node
16 | }
17 |
18 | export default WellSection
19 |
--------------------------------------------------------------------------------
/static/src/js/components/FilterStack/FilterStack.scss:
--------------------------------------------------------------------------------
1 | .filter-stack {
2 | height: 0;
3 | width: 0;
4 | margin: 0;
5 | padding: 0;
6 | list-style: none;
7 | background-color: $color__black--900;
8 | border-top: 1px solid $color__black--700;
9 | color: $color__white;
10 | visibility: hidden;
11 |
12 | &--is-open {
13 | height: auto;
14 | width: 100%;
15 | visibility: visible;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/cypress/integration/retrievals/download_mocks/retrievals.json:
--------------------------------------------------------------------------------
1 | {
2 | "body": {"id":"0684513294","user_id":20,"environment":"prod","jsondata":{"source":"?p=!C1443528505-LAADS&sb=-77.15071678161621%2C38.78817179999825%2C-76.89801406860352%2C38.99784152603538&m=38.379638671875!-77.1767578125!7!1!0!0%2C2&qt=2020-01-06T04%3A15%3A27.310Z%2C2020-01-13T07%3A32%3A50.962Z&ff=Map%20Imagery&tl=1563377338!4!!","portal_id":"","shapefile_id":""}}
3 | }
4 |
--------------------------------------------------------------------------------
/portals/idn/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "hasStyles": true,
3 | "logo": {
4 | "id": "idn-logo",
5 | "link": "https://idn.ceos.org/",
6 | "title": "CEOS IDN Search"
7 | },
8 | "org": "IDN",
9 | "pageTitle": "IDN",
10 | "parentConfig": "edsc",
11 | "query": {
12 | "hasGranulesOrCwic": null
13 | },
14 | "ui": {
15 | "showOnlyGranulesCheckbox": false,
16 | "showNonEosdisCheckbox": false
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/static/src/css/vendor/bootstrap/_utils.scss:
--------------------------------------------------------------------------------
1 | /*
2 | * Bootstrap Utils
3 | */
4 |
5 | @import "~bootstrap/scss/_utilities";
6 | @import "~bootstrap/scss/_reboot";
7 | @import "~bootstrap/scss/_print";
8 | @import "~bootstrap/scss/_grid";
9 | @import "~bootstrap/scss/_images";
10 | @import "~bootstrap/scss/_media";
11 | @import "~bootstrap/scss/_type";
12 | @import "~bootstrap/scss/_forms";
13 | @import "~bootstrap/scss/_buttons";
14 |
--------------------------------------------------------------------------------
/static/src/js/components/EDSCModal/EDSCModalOverlay.scss:
--------------------------------------------------------------------------------
1 | .edsc-modal-overlay {
2 | display: flex;
3 | flex-direction: column;
4 | position: absolute;
5 | top: 0;
6 | left: 0;
7 | right: 0;
8 | bottom: 0;
9 | padding: 1rem;
10 | background-color: #fff;
11 | z-index: 1;
12 | overflow: scroll;
13 |
14 | &__content {
15 | flex-grow: 1;
16 | }
17 |
18 | &__heading {
19 | flex-grow: 0;
20 | }
21 | }
--------------------------------------------------------------------------------
/static/src/js/util/request/accessMethodsRequest.js:
--------------------------------------------------------------------------------
1 | import Request from './request'
2 | import { getEnvironmentConfig } from '../../../../../sharedUtils/config'
3 |
4 | export default class AccessMethodsRequest extends Request {
5 | constructor(authToken) {
6 | super(getEnvironmentConfig().apiHost)
7 | this.authenticated = true
8 | this.authToken = authToken
9 | this.searchPath = 'access_methods'
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/static/src/js/util/url/integerEncoders.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Decodes an integer parameter (returns the same value as an integer)
3 | * @param {Integer} integer
4 | */
5 | export const decodeInteger = integer => parseInt(integer, 10) || undefined
6 |
7 |
8 | /**
9 | * Encodes an integer parameter (returns the same value as a string)
10 | * @param {Integer} integer
11 | */
12 | export const encodeInteger = integer => integer || ''
13 |
--------------------------------------------------------------------------------
/migrations/1565744336991_add-default-to-order-state.js:
--------------------------------------------------------------------------------
1 | exports.shorthands = undefined
2 |
3 | exports.up = (pgm) => {
4 | pgm.db.query('ALTER TABLE retrieval_orders ALTER COLUMN state SET DEFAULT \'creating\';')
5 | pgm.db.query('UPDATE retrieval_orders SET state=\'creating\' WHERE state IS NULL;')
6 | }
7 |
8 | exports.down = (pgm) => {
9 | pgm.db.query('ALTER TABLE retrieval_orders ALTER COLUMN state DROP DEFAULT;')
10 | }
11 |
--------------------------------------------------------------------------------
/serverless/src/util/echoForms/__tests__/getEmail.test.js:
--------------------------------------------------------------------------------
1 | import { echoFormWithEmail } from './mocks'
2 | import { getEmail } from '../getEmail'
3 |
4 | describe('util#getEmail', () => {
5 | test('correctly discovers the correct fields from the provided xml', () => {
6 | const email = getEmail(echoFormWithEmail)
7 |
8 | expect(email).toEqual({
9 | EMAIL: 'edsc-support@earthdata.nasa.gov'
10 | })
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/static/src/js/components/Well/WellFooter.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | export const WellFooter = ({
5 | children
6 | }) => (
7 |
10 | )
11 |
12 | WellFooter.defaultProps = {
13 | children: null
14 | }
15 |
16 | WellFooter.propTypes = {
17 | children: PropTypes.node
18 | }
19 |
20 | export default WellFooter
21 |
--------------------------------------------------------------------------------
/static/src/js/components/Well/WellSection.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | export const WellSection = ({
5 | children
6 | }) => (
7 |
8 | {children}
9 |
10 | )
11 |
12 | WellSection.defaultProps = {
13 | children: null
14 | }
15 |
16 | WellSection.propTypes = {
17 | children: PropTypes.node
18 | }
19 |
20 | export default WellSection
21 |
--------------------------------------------------------------------------------
/static/src/css/vendor/bootstrap/_vars.scss:
--------------------------------------------------------------------------------
1 | /*
2 | * Bootstrap Variables
3 | *
4 | * The default bootstrap variables are loaded in via the 'sass-resources-loader'
5 | */
6 |
7 | // Override default Bootstrap variables
8 | @import "overrides";
9 |
10 | // Import default Bootstrap functions, variables, and mixins
11 | @import "~bootstrap/scss/_functions";
12 | @import "~bootstrap/scss/_variables";
13 | @import "~bootstrap/scss/_mixins";
14 |
--------------------------------------------------------------------------------
/static/src/js/util/autocompleteFacetsMap.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Maps the CMR Autocomplete types to CMR Facet types
3 | */
4 | export const autocompleteFacetsMap = {
5 | organization: 'data_center_h',
6 | instrument: 'instrument_h',
7 | granule_data_format: 'granule_data_format_h',
8 | platform: 'platform_h',
9 | processing_level_id: 'processing_level_id_h',
10 | project: 'project_h',
11 | science_keywords: 'science_keywords_h'
12 | }
13 |
--------------------------------------------------------------------------------
/static/src/js/util/convertRemsToPixels.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Given the a number in rems, returns that number in pixels relative to the current
3 | * body font size.
4 | * @param {Number} rem The number in rems.
5 | * @returns {Number} The number in pixels.
6 | */
7 | export const convertRemsToPixels = (rem) => {
8 | if (!window || !document) return 16
9 | return rem * parseFloat(getComputedStyle(document.documentElement).fontSize)
10 | }
11 |
--------------------------------------------------------------------------------
/static/src/js/reducers/admin/isAuthorized.js:
--------------------------------------------------------------------------------
1 | import { SET_ADMIN_IS_AUTHORIZED } from '../../constants/actionTypes'
2 |
3 | const initialState = false
4 |
5 | const adminIsAuthorizedReducer = (state = initialState, action) => {
6 | switch (action.type) {
7 | case SET_ADMIN_IS_AUTHORIZED: {
8 | return action.payload
9 | }
10 | default:
11 | return state
12 | }
13 | }
14 |
15 | export default adminIsAuthorizedReducer
16 |
--------------------------------------------------------------------------------
/static/src/js/util/__tests__/pathStartsWith.test.js:
--------------------------------------------------------------------------------
1 | import { pathStartsWith } from '../pathStartsWith'
2 |
3 | describe('pathStartsWith', () => {
4 | test('returns true when a path is matched', () => {
5 | expect(pathStartsWith('/test/path', '/test')).toEqual(true)
6 | })
7 |
8 | test('returns false with a path that does not match', () => {
9 | expect(pathStartsWith('/test/path', '/wrongtest')).toEqual(false)
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/static/src/js/util/buildPromise.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Builds a promise object with a resolve method to manually create/return promises
3 | * @param {Object} response The value to return when the promise is resolved
4 | * @see {@link https://stackoverflow.com/questions/27715275/whats-the-difference-between-returning-value-or-promise-resolve-from-then|Stack Overflow}
5 | */
6 | export const buildPromise = response => new Promise(resolve => resolve(response))
7 |
--------------------------------------------------------------------------------
/static/src/js/util/request/providerRequest.js:
--------------------------------------------------------------------------------
1 | import Request from './request'
2 | import { getEnvironmentConfig } from '../../../../../sharedUtils/config'
3 |
4 | export default class ProviderRequest extends Request {
5 | constructor(authToken) {
6 | super(getEnvironmentConfig().apiHost)
7 | this.authenticated = true
8 | this.authToken = authToken
9 | }
10 |
11 | all() {
12 | return this.get('providers')
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/static/src/js/util/request/regionRequest.js:
--------------------------------------------------------------------------------
1 | import Request from './request'
2 | import { getEnvironmentConfig } from '../../../../../sharedUtils/config'
3 |
4 | export default class RegionRequest extends Request {
5 | constructor() {
6 | super(getEnvironmentConfig().apiHost)
7 | this.lambda = true
8 | this.searchPath = 'regions'
9 | }
10 |
11 | search(params) {
12 | return this.get(this.searchPath, params)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/serverless/src/util/echoForms/__tests__/getBoundingBox.test.js:
--------------------------------------------------------------------------------
1 | import { echoFormXml } from './mocks'
2 | import { getBoundingBox } from '../getBoundingBox'
3 |
4 | describe('util#getBoundingBox', () => {
5 | test('correctly discovers the correct fields from the provided xml', () => {
6 | const boundingBox = getBoundingBox(echoFormXml)
7 |
8 | expect(boundingBox).toEqual({
9 | BBOX: '-180,-90,180,90'
10 | })
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/static/src/js/util/map/actions/panBoundsToCenter.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Pans the Leaflet bounds to the center with padding.
3 | * @param {Object} map - An instance of the Leaflet map.
4 | * @param {Object} bounds - The bounds of the area to be centered.
5 | */
6 | export const panBoundsToCenter = (map, bounds) => {
7 | if (bounds.isValid && bounds.isValid()) {
8 | map.fitBounds(bounds, { padding: [200, 200] }).panTo(bounds.getCenter())
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/serverless/src/util/echoForms/__tests__/getSwitchFields.test.js:
--------------------------------------------------------------------------------
1 | import { echoFormXml } from './mocks'
2 | import { getSwitchFields } from '../getSwitchFields'
3 |
4 | describe('util#getSwitchFields', () => {
5 | test('correctly discovers the correct fields from the provided xml', () => {
6 | const topLevelFields = getSwitchFields(echoFormXml)
7 |
8 | expect(topLevelFields).toEqual({
9 | INCLUDE_META: 'N'
10 | })
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/serverless/src/util/generateFormDigest.js:
--------------------------------------------------------------------------------
1 | import forge from 'node-forge'
2 |
3 | /**
4 | * Generates a SHA1 digest of the echoform, used to determine if the saved
5 | * EchoForm data is up to date with the current EchoForm
6 | * @param {String} form EchoForm string
7 | */
8 | export const generateFormDigest = (form) => {
9 | const formDigest = forge.md.sha1.create()
10 | formDigest.update(form)
11 |
12 | return formDigest.digest().toHex()
13 | }
14 |
--------------------------------------------------------------------------------
/static/src/js/components/Dropzone/ShapefileDropzone.scss:
--------------------------------------------------------------------------------
1 | .shapefile-dropzone {
2 | display: none;
3 | z-index: 5000;
4 |
5 | &.dropzone--is-active {
6 | display: block;
7 | position: absolute;
8 | top: 0;
9 | left: 0;
10 | right: 0;
11 | bottom: 0;
12 | background-color: transparent;
13 | }
14 |
15 | // Hides the default text inside the dropzone container
16 | .dz-default {
17 | @include invisible(hidden)
18 | }
19 | }
--------------------------------------------------------------------------------
/static/src/js/components/Well/WellIntroduction.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | export const WellIntroduction = ({
5 | children
6 | }) => (
7 |
8 | {children}
9 |
10 | )
11 |
12 | WellIntroduction.defaultProps = {
13 | children: null
14 | }
15 |
16 | WellIntroduction.propTypes = {
17 | children: PropTypes.node
18 | }
19 |
20 | export default WellIntroduction
21 |
--------------------------------------------------------------------------------
/static/src/js/util/__tests__/isNumber.test.js:
--------------------------------------------------------------------------------
1 | import { isNumber } from '../isNumber'
2 |
3 | describe('isNumber', () => {
4 | describe('when provided a non number string', () => {
5 | test('returns false', () => {
6 | expect(isNumber('test')).toEqual(false)
7 | })
8 | })
9 |
10 | describe('when provided a number string', () => {
11 | test('returns true', () => {
12 | expect(isNumber('100')).toEqual(true)
13 | })
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/static/src/js/components/ContactInfo/ContactInfo.scss:
--------------------------------------------------------------------------------
1 | .contact-info-form {
2 | border-radius: 5px;
3 | padding: 30px;
4 | border-color: #e3e3e3;
5 | color: #333333;
6 |
7 | &__list {
8 | list-style: none;
9 | padding: 0;
10 |
11 | &__item {
12 | margin: 10px, 0;
13 | display: list-item;
14 | }
15 | }
16 |
17 | &__label {
18 | display: inline-block;
19 | min-width: 200px;
20 | font-weight: bold;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/static/src/js/util/request/preferencesRequest.js:
--------------------------------------------------------------------------------
1 | import Request from './request'
2 | import { getEnvironmentConfig } from '../../../../../sharedUtils/config'
3 |
4 | export default class PreferencesRequest extends Request {
5 | constructor(authToken) {
6 | super(getEnvironmentConfig().apiHost)
7 | this.authenticated = true
8 | this.authToken = authToken
9 | }
10 |
11 | update(params) {
12 | return this.post('preferences', params)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/static/src/js/util/request/dataQualitySummaryRequest.js:
--------------------------------------------------------------------------------
1 | import Request from './request'
2 | import { getEnvironmentConfig } from '../../../../../sharedUtils/config'
3 |
4 | export default class DataQualitySummaryRequest extends Request {
5 | constructor(authToken) {
6 | super(getEnvironmentConfig().apiHost)
7 | this.authenticated = true
8 | this.authToken = authToken
9 | }
10 |
11 | fetch(params) {
12 | return this.post('dqs', params)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/static/src/js/reducers/browser.js:
--------------------------------------------------------------------------------
1 | import {
2 | UPDATE_BROWSER_VERSION
3 | } from '../constants/actionTypes'
4 |
5 | const initialState = {}
6 |
7 | const browserReducer = (state = initialState, action) => {
8 | switch (action.type) {
9 | case UPDATE_BROWSER_VERSION: {
10 | const { payload } = action
11 | return {
12 | ...payload
13 | }
14 | }
15 | default:
16 | return state
17 | }
18 | }
19 |
20 | export default browserReducer
21 |
--------------------------------------------------------------------------------
/sharedUtils/limitedCollectionSize.js:
--------------------------------------------------------------------------------
1 | import { getValueForTag } from './tags'
2 |
3 | /**
4 | * Retrieve the value stored within the limited collection tag for a provided collection
5 | * @param {Object} collectionMetadata CMR Collection metadata
6 | */
7 | export const limitedCollectionSize = (collectionMetadata) => {
8 | const { tags } = collectionMetadata
9 |
10 | const { limit } = getValueForTag('limited_collections', tags, 'edsc') || {}
11 |
12 | return limit
13 | }
14 |
--------------------------------------------------------------------------------
/static/src/js/components/ProgressRing/ProgressRing.scss:
--------------------------------------------------------------------------------
1 | .progress-ring {
2 | &__ring {
3 | position: relative;
4 | }
5 |
6 | &__progress {
7 | z-index: 1;
8 | position: relative;
9 | stroke: $color__green;
10 | transition: stroke-dashoffset 0.35s;
11 | transform: rotate(-90deg);
12 | transform-origin: 50% 50%,
13 | }
14 |
15 | &__circle-back {
16 | z-index: 0;
17 | position: relative;
18 | stroke: $color__black--200;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/portals/podaac/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "hasStyles": true,
3 | "logo": {
4 | "id": "podaac-logo",
5 | "link": "https://podaac.jpl.nasa.gov",
6 | "title": "PO.DAAC Home"
7 | },
8 | "org": "PO.DAAC",
9 | "pageTitle": "PO.DAAC",
10 | "parentConfig": "edsc",
11 | "query": {
12 | "dataCenter": "NASA/JPL/PODAAC",
13 | "hasGranulesOrCwic": null
14 | },
15 | "ui": {
16 | "showOnlyGranulesCheckbox": false,
17 | "showNonEosdisCheckbox": false
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/serverless/src/util/echoForms/getEmail.js:
--------------------------------------------------------------------------------
1 | import { getFieldElementValue } from './getFieldElementValue'
2 |
3 | /**
4 | * Get the email address from the provided XML Document
5 | * @param {String} xmlDocument ECHO Form xml as a string
6 | */
7 | export const getEmail = (xmlDocument) => {
8 | const xmlElement = getFieldElementValue(xmlDocument, 'email')
9 |
10 | if (xmlElement.length) {
11 | return {
12 | EMAIL: xmlElement
13 | }
14 | }
15 |
16 | return {}
17 | }
18 |
--------------------------------------------------------------------------------
/static/src/js/components/AccessMethod/EchoForm.scss:
--------------------------------------------------------------------------------
1 | #root {
2 | .tree-item__parent-button {
3 | &.btn {
4 | height: 1.5rem;
5 | }
6 |
7 | svg {
8 | vertical-align: initial;
9 | }
10 | }
11 | }
12 |
13 | .echoform {
14 | .card {
15 | background-color: transparent;
16 | }
17 |
18 | & > .card {
19 | border: 0;
20 | background-color: transparent;
21 | }
22 |
23 | .form__body {
24 | border: 0;
25 | padding: 0;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/static/src/js/util/request/retrievalCollectionRequest.js:
--------------------------------------------------------------------------------
1 | import Request from './request'
2 | import { getEnvironmentConfig } from '../../../../../sharedUtils/config'
3 |
4 | export default class RetrievalCollectionRequest extends Request {
5 | constructor(authToken) {
6 | super(getEnvironmentConfig().apiHost)
7 | this.authenticated = true
8 | this.authToken = authToken
9 | }
10 |
11 | fetch(id) {
12 | return this.get(`/retrieval_collections/${id}`)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/custom.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Custom issue template
3 | about: Template for general tasks and improvements
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | Please review [Contribution.md](https://github.com/nasa/earthdata-search/blob/master/CONTRIBUTING.md) before contributing to this project.
11 |
12 | ### Description
13 | Describe the purpose of the issue
14 |
15 | ### Files
16 | Attach any relevant images or files
17 |
18 | ### Acceptance Criteria
19 |
--------------------------------------------------------------------------------
/migrations/1598036919806_add-parent-shapefile-id-selected-features-to-shapefiles.js:
--------------------------------------------------------------------------------
1 | exports.shorthands = undefined
2 |
3 | exports.up = (pgm) => {
4 | pgm.addColumns('shapefiles', {
5 | parent_shapefile_id: {
6 | type: 'integer',
7 | references: 'shapefiles'
8 | },
9 | selected_features: {
10 | type: 'text []'
11 | }
12 | })
13 | }
14 |
15 | exports.down = (pgm) => {
16 | pgm.dropColumns('shapefiles', ['parent_shapefile_id', 'selected_features'])
17 | }
18 |
--------------------------------------------------------------------------------
/portals/soos/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "hasStyles": true,
3 | "logo": {
4 | "id": "SOOS-logo",
5 | "link": "http://www.soos.aq",
6 | "title": "SOOS Metadata Search"
7 | },
8 | "org": "SOOS",
9 | "pageTitle": "Southern Ocean Observing System",
10 | "parentConfig": "edsc",
11 | "query": {
12 | "tagKey": "aq.soos",
13 | "hasGranulesOrCwic": null
14 | },
15 | "ui": {
16 | "showOnlyGranulesCheckbox": false,
17 | "showNonEosdisCheckbox": false
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/static/src/js/components/CollectionDetails/RelatedUrlsModal.scss:
--------------------------------------------------------------------------------
1 | .related-urls-modal {
2 | &__group {
3 | margin-bottom: $spacer*1.5;
4 | }
5 |
6 | &__group-title {
7 | padding-bottom: $spacer/2;
8 | font-size: 1rem;
9 | border-bottom: 1px solid $color__black--200;
10 | }
11 |
12 | &__url {
13 | margin: 0;
14 | margin-bottom: $spacer/2;
15 | padding: 0;
16 | list-style: none;
17 | }
18 |
19 | &__link {
20 | word-break: break-all;
21 | }
22 | }
--------------------------------------------------------------------------------
/static/src/js/components/SpatialDisplay/SpatialDisplayEntry.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | const SpatialDisplayEntry = (props) => {
5 | const { children } = props
6 |
7 | return (
8 | <>
9 | {children}
10 | >
11 | )
12 | }
13 |
14 | SpatialDisplayEntry.defaultProps = {
15 | children: null
16 | }
17 |
18 | SpatialDisplayEntry.propTypes = {
19 | children: PropTypes.node
20 | }
21 |
22 | export default SpatialDisplayEntry
23 |
--------------------------------------------------------------------------------
/static/src/js/containers/MapContainer/MapContainer.scss:
--------------------------------------------------------------------------------
1 | .map {
2 | position: absolute;
3 | top: 0;
4 | left: 0;
5 | bottom: 0;
6 | right: 0;
7 | height: 100%;
8 | width: 100%;
9 | display: flex;
10 | align-items: center;
11 | justify-content: center;
12 | flex-basis: auto;
13 | background: #FEFEFE;
14 | color: #DDD;
15 | font-weight: bold;
16 | z-index: 0;
17 | background: #000;
18 |
19 | .is-panels-dragging & {
20 | pointer-events: none;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/static/src/js/components/CollectionDetails/CollectionDetailsFeatureGroup.js:
--------------------------------------------------------------------------------
1 | import {
2 | MapLayer,
3 | withLeaflet
4 | } from 'react-leaflet'
5 |
6 | import { buildLayer } from '../../util/map/layers'
7 |
8 | export class FeatureGroup extends MapLayer {
9 | createLeafletElement({ metadata }) {
10 | const featureGroup = buildLayer({ color: '#54F7A3', fillOpacity: 0.4, weight: 1 }, metadata)
11 | return featureGroup
12 | }
13 | }
14 |
15 | export default withLeaflet(FeatureGroup)
16 |
--------------------------------------------------------------------------------
/static/src/js/middleware/metrics/constants.js:
--------------------------------------------------------------------------------
1 | export const METRICS_CLICK = 'METRICS_CLICK'
2 | export const METRICS_COLLECTION_SORT_CHANGE = 'METRICS_COLLECTION_SORT_CHANGE'
3 | export const METRICS_DATA_ACCESS = 'METRICS_DATA_ACCESS'
4 | export const METRICS_INITIAL_RENDER = 'METRICS_INITIAL_RENDER'
5 | export const METRICS_MAP = 'METRICS_MAP'
6 | export const METRICS_SPATIAL_EDIT = 'METRICS_SPATIAL_EDIT'
7 | export const METRICS_TIMELINE = 'METRICS_TIMELINE'
8 | export const METRICS_TIMING = 'METRICS_TIMING'
9 |
--------------------------------------------------------------------------------
/static/src/js/util/request/shapefileRequest.js:
--------------------------------------------------------------------------------
1 | import Request from './request'
2 | import { getEnvironmentConfig } from '../../../../../sharedUtils/config'
3 |
4 | export default class ShapefileRequest extends Request {
5 | constructor() {
6 | super(getEnvironmentConfig().apiHost)
7 | this.lambda = true
8 | }
9 |
10 | fetch(shapefileId) {
11 | return this.get(`shapefiles/${shapefileId}`)
12 | }
13 |
14 | save(params) {
15 | return this.post('shapefiles', params)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/portals/ornldaac/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "hasStyles": true,
3 | "hasScripts": true,
4 | "logo": {
5 | "id": "ornl-daac-logo",
6 | "link": "https://daac.ornl.gov",
7 | "title": "ORNL DAAC Home"
8 | },
9 | "org": "ORNL DAAC",
10 | "pageTitle": "ORNL DAAC",
11 | "parentConfig": "edsc",
12 | "query": {
13 | "dataCenter": "ORNL_DAAC",
14 | "hasGranulesOrCwic": null
15 | },
16 | "ui": {
17 | "showOnlyGranulesCheckbox": false,
18 | "showNonEosdisCheckbox": false
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/static/src/js/util/focusedGranule.js:
--------------------------------------------------------------------------------
1 | import { isEmpty } from 'lodash'
2 |
3 | /**
4 | * Returns the collection object from the metadata store for the provided granuleId
5 | * @param {String} granuleId Focused collection id
6 | * @param {Object} granules granules from the metadata store
7 | */
8 | export const getFocusedGranuleObject = (granuleId, granules) => {
9 | if (isEmpty(granules)) return undefined
10 |
11 | return granules[granuleId] || {}
12 | }
13 |
14 | export default getFocusedGranuleObject
15 |
--------------------------------------------------------------------------------
/portals/cwic/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "hasStyles": true,
3 | "logo": {
4 | "id": "ceos-logo",
5 | "link": "http://ceos.org/ourwork/workinggroups/wgiss/access/cwic/",
6 | "title": "CEOS Home"
7 | },
8 | "org": "CWIC",
9 | "pageTitle": "CWIC",
10 | "parentConfig": "edsc",
11 | "query": {
12 | "tagKey": "org.ceos.wgiss.cwic.granules.prod",
13 | "hasGranulesOrCwic": null
14 | },
15 | "ui": {
16 | "showOnlyGranulesCheckbox": false,
17 | "showNonEosdisCheckbox": false
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/static/src/js/util/isLinkType.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns true the current collection link matches.
3 | * This expects the the link follows this format: http://www.somelink.com/example/{link type}#
4 | * @param {string} link - The link to be checked.
5 | * @param {string} linkType - The link type to be checked.
6 | * @return {boolean}
7 | */
8 | export const isLinkType = (link = '', linkType) => {
9 | if (link.indexOf(`${linkType}#`) > -1) return true
10 | return false
11 | }
12 |
13 | export default isLinkType
14 |
--------------------------------------------------------------------------------
/static/src/js/util/query.js:
--------------------------------------------------------------------------------
1 | import history from './history'
2 |
3 | export const addQueryParam = (query) => {
4 | const location = Object.assign({}, history.getCurrentLocation())
5 |
6 | Object.assign(location.query, query)
7 |
8 | history.push(location)
9 | }
10 |
11 | export const removeQueryParam = (...queryKeys) => {
12 | const location = Object.assign({}, history.getCurrentLocation())
13 |
14 | queryKeys.forEach(queryKey => delete location.query[queryKey])
15 |
16 | history.push(location)
17 | }
18 |
--------------------------------------------------------------------------------
/static/src/js/util/request/logoutRequest.js:
--------------------------------------------------------------------------------
1 | import Request from './request'
2 | import { getEnvironmentConfig } from '../../../../../sharedUtils/config'
3 |
4 | /**
5 | * Request object for concept specific requests
6 | */
7 | export default class LogoutRequest extends Request {
8 | constructor(authToken) {
9 | super(getEnvironmentConfig().apiHost)
10 |
11 | this.authenticated = true
12 | this.authToken = authToken
13 | }
14 |
15 | logout() {
16 | return this.delete('logout')
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/static/src/js/util/collectionMetadata/granuleLimit.js:
--------------------------------------------------------------------------------
1 | import { limitedCollectionSize } from '../../../../../sharedUtils/limitedCollectionSize'
2 | import { getApplicationConfig } from '../../../../../sharedUtils/config'
3 |
4 | /**
5 | * Returns the value of the edsc.limited_collections tag or the defaultMaxOrderSize
6 | * @param {Object} metadata Collection metadata
7 | */
8 | export const getGranuleLimit = (metadata = {}) => (
9 | limitedCollectionSize(metadata) || getApplicationConfig().defaultMaxOrderSize
10 | )
11 |
--------------------------------------------------------------------------------
/static/src/js/util/map/actions/panFeatureGroupToCenter.js:
--------------------------------------------------------------------------------
1 | import { panBoundsToCenter } from './panBoundsToCenter'
2 |
3 | /**
4 | * Pans the Leaflet map to the center with padding.
5 | * @param {Object} map - An instance of the Leaflet map.
6 | * @param {Object} featureGroup - The feature group to be centered.
7 | */
8 | export const panFeatureGroupToCenter = (map, featureGroup) => {
9 | if (!featureGroup.getBounds) return
10 | const bounds = featureGroup.getBounds()
11 | panBoundsToCenter(map, bounds)
12 | }
13 |
--------------------------------------------------------------------------------
/serverless/src/util/cmr/__tests__/prepareExposeHeaders.test.js:
--------------------------------------------------------------------------------
1 | import { prepareExposeHeaders } from '../prepareExposeHeaders'
2 |
3 | describe('util#prepareExposeHeaders', () => {
4 | test('appends jwt-token to expose headers when they exist', () => {
5 | expect(prepareExposeHeaders({ 'access-control-expose-headers': 'header-1' })).toEqual('header-1, jwt-token')
6 | })
7 |
8 | test('returns jwt-token when no expose headers exist', () => {
9 | expect(prepareExposeHeaders({})).toEqual('jwt-token')
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/serverless/src/util/echoForms/__tests__/getNameValuePairsForResample.test.js:
--------------------------------------------------------------------------------
1 | import { loadedEchoFormXml } from './mocks'
2 | import { getNameValuePairsForResample } from '../getNameValuePairsForResample'
3 |
4 | describe('util#getNameValuePairsForResample', () => {
5 | test('correctly discovers the correct fields from the provided xml', () => {
6 | const projectPairs = getNameValuePairsForResample(loadedEchoFormXml)
7 |
8 | expect(projectPairs).toEqual({
9 | RESAMPLE: 'PERCENT:100'
10 | })
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/serverless/src/util/getVerifiedJwtToken.js:
--------------------------------------------------------------------------------
1 | import jwt from 'jsonwebtoken'
2 |
3 | import { getSecretEarthdataConfig } from '../../../sharedUtils/config'
4 | import { cmrEnv } from '../../../sharedUtils/cmrEnv'
5 |
6 | /**
7 | * Verifies the JWT Token and returns the contents
8 | * @param {String} jwtToken
9 | */
10 | export const getVerifiedJwtToken = (jwtToken) => {
11 | const { secret } = getSecretEarthdataConfig(cmrEnv())
12 | const verifiedJwtToken = jwt.verify(jwtToken, secret)
13 | return verifiedJwtToken
14 | }
15 |
--------------------------------------------------------------------------------
/static/src/js/components/Facets/FacetsSectionHeading.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | import './FacetsSectionHeading.scss'
5 |
6 | export const FacetsSectionHeading = ({ id, letter }) => (
7 |
11 | {letter}
12 |
13 | )
14 |
15 | FacetsSectionHeading.propTypes = {
16 | id: PropTypes.string.isRequired,
17 | letter: PropTypes.string.isRequired
18 | }
19 |
20 | export default FacetsSectionHeading
21 |
--------------------------------------------------------------------------------
/portals/carve/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "hasStyles": true,
3 | "hasScripts": true,
4 | "logo": {
5 | "id": "ornl-daac-logo",
6 | "link": "https://carve.ornl.gov",
7 | "title": "ORNL DAAC CARVE Home"
8 | },
9 | "org": "ORNL DAAC",
10 | "pageTitle": "CARVE",
11 | "parentConfig": "edsc",
12 | "query": {
13 | "project": "CARVE",
14 | "hasGranulesOrCwic": null
15 | },
16 | "title": "CARVE",
17 | "ui": {
18 | "showOnlyGranulesCheckbox": false,
19 | "showNonEosdisCheckbox": false
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/serverless-infrastructure.yml:
--------------------------------------------------------------------------------
1 | service: earthdata-search-infrastructure
2 |
3 | provider:
4 | name: aws
5 | runtime: nodejs10.x
6 | stage: ${opt:stage, 'lab'}
7 | region: us-east-1
8 | vpc:
9 | securityGroupIds:
10 | - Ref: LambdaSecurityGroup
11 | subnetIds:
12 | - ${env:SUBNET_ID_A}
13 | - ${env:SUBNET_ID_B}
14 |
15 | role: EDSCServerlessAppRole
16 |
17 | #
18 | # AWS Infrastructure Resources
19 | #
20 | resources: ${file(./serverless-configs/${self:provider.name}-infrastructure-resources.yml)}
--------------------------------------------------------------------------------
/static/src/js/util/datepicker.js:
--------------------------------------------------------------------------------
1 | import moment from 'moment'
2 |
3 | const isCustomTime = (momentObj) => {
4 | if (!moment.isMoment(momentObj)) return true
5 |
6 | const selectedDate = moment(momentObj)
7 | const startOfSelectedDate = moment(selectedDate).clone().startOf('day')
8 | const endOfSelectedDate = moment(selectedDate).clone().endOf('day')
9 |
10 | return !selectedDate.isSame(startOfSelectedDate, 'second')
11 | && !selectedDate.isSame(endOfSelectedDate, 'second')
12 | }
13 |
14 | export default isCustomTime
15 |
--------------------------------------------------------------------------------
/serverless/src/decodeId/handler.js:
--------------------------------------------------------------------------------
1 | import { deobfuscateId } from '../util/obfuscation/deobfuscateId'
2 |
3 | const decodeId = async (event) => {
4 | const { queryStringParameters } = event
5 | const {
6 | obfuscated_id: obfuscatedId,
7 | spin = process.env.obfuscationSpin
8 | } = queryStringParameters
9 |
10 | return {
11 | isBase64Encoded: false,
12 | statusCode: 200,
13 | body: JSON.stringify({
14 | id: deobfuscateId(obfuscatedId, spin)
15 | })
16 | }
17 | }
18 |
19 | export default decodeId
20 |
--------------------------------------------------------------------------------
/serverless/src/util/__tests__/getVerifiedJwtToken.test.js:
--------------------------------------------------------------------------------
1 | import { getVerifiedJwtToken } from '../getVerifiedJwtToken'
2 | import * as getEarthdataConfig from '../../../../sharedUtils/config'
3 |
4 | describe('getVerifiedJwtToken', () => {
5 | test('returns the contents of the jwtToken', () => {
6 | const { jwtToken } = getEarthdataConfig.getEnvironmentConfig('test')
7 |
8 | expect(getVerifiedJwtToken(jwtToken)).toEqual({
9 | id: 1,
10 | username: 'testuser',
11 | iat: 1578433476
12 | })
13 | })
14 | })
15 |
--------------------------------------------------------------------------------
/static/src/js/components/AdvancedSearchDisplay/AdvancedSearchDisplayEntry.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { pure } from 'recompose'
3 | import PropTypes from 'prop-types'
4 |
5 |
6 | const AdvancedSearchDisplayEntry = pure(({
7 | children
8 | }) => (
9 | <>
10 | { children }
11 | >
12 | ))
13 |
14 | AdvancedSearchDisplayEntry.defaultProps = {
15 | children: null
16 | }
17 |
18 | AdvancedSearchDisplayEntry.propTypes = {
19 | children: PropTypes.node
20 | }
21 |
22 | export default AdvancedSearchDisplayEntry
23 |
--------------------------------------------------------------------------------
/static/src/js/components/DownloadHistory/DownloadHistory.scss:
--------------------------------------------------------------------------------
1 | .download-history {
2 | &__spinner {
3 | margin: 0 auto;
4 | }
5 | }
6 |
7 | .order-status-table {
8 | a {
9 | color: $color__blue;
10 |
11 | &:hover,
12 | &:active {
13 | color: $color__blue--dark;
14 | }
15 | }
16 |
17 | &__ago {
18 | white-space: nowrap;
19 | }
20 |
21 | &__remove {
22 | color: $color__black--300;
23 |
24 | &:hover,
25 | &:active {
26 | color: $color__black--600;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/static/src/js/components/Panels/PanelGroup.scss:
--------------------------------------------------------------------------------
1 | .panel-group {
2 | position: absolute;
3 | top: 0;
4 | display: flex;
5 | flex-direction: column;
6 | height: 100%;
7 | width: 100%;
8 | background-color: $color__white;
9 | z-index: 0;
10 | opacity: 0;
11 |
12 | &--is-active {
13 | z-index: 1;
14 | }
15 |
16 | &--is-open {
17 | opacity: 1;
18 |
19 | .panels--will-minimize & {
20 | opacity: 0.25;
21 | }
22 |
23 | .panels--is-minimized & {
24 | opacity: 0;
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/portals/airmoss/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "hasStyles": true,
3 | "hasScripts": true,
4 | "logo": {
5 | "id": "ornl-daac-logo",
6 | "link": "https://airmoss.ornl.gov",
7 | "title": "ORNL DAAC AirMOSS Home"
8 | },
9 | "org": "ORNL DAAC",
10 | "pageTitle": "AirMOSS",
11 | "parentConfig": "edsc",
12 | "query": {
13 | "project": "AirMOSS",
14 | "hasGranulesOrCwic": null
15 | },
16 | "title": "AirMOSS",
17 | "ui": {
18 | "showOnlyGranulesCheckbox": false,
19 | "showNonEosdisCheckbox": false
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/static/src/js/components/CollectionDownloadDisplay/CollectionDownloadDisplay.scss:
--------------------------------------------------------------------------------
1 | .collection-download-display {
2 | &__header {
3 | margin: $spacer;
4 | }
5 |
6 | &__intro {
7 | align-items: center;
8 | }
9 |
10 | &__button {
11 | margin-left: $spacer/2;
12 | }
13 |
14 | &__body {
15 | margin: $spacer;
16 | }
17 |
18 | &__list {
19 | margin: 0;
20 | padding-left: $spacer;
21 | }
22 |
23 | pre {
24 | font-size: .7em;
25 | white-space: pre-wrap;
26 | word-wrap: break-word;
27 | }
28 | }
--------------------------------------------------------------------------------
/cypress/support/getJwtToken.js:
--------------------------------------------------------------------------------
1 | import jwt from 'jsonwebtoken'
2 |
3 | import { getSecretEarthdataConfig, getSecretCypressConfig } from '../../sharedUtils/config'
4 | import { cmrEnv } from '../../sharedUtils/cmrEnv'
5 |
6 | /**
7 | * Creates a jwtToken based on the Cypress user config in secret.config.json
8 | */
9 | export const getJwtToken = () => {
10 | const { secret } = getSecretEarthdataConfig(cmrEnv())
11 | const { user } = getSecretCypressConfig()
12 | const jwtToken = jwt.sign({ ...user }, secret)
13 |
14 | return jwtToken
15 | }
16 |
--------------------------------------------------------------------------------
/serverless/src/util/__tests__/getJwtToken.test.js:
--------------------------------------------------------------------------------
1 | import { getJwtToken } from '../getJwtToken'
2 |
3 | beforeEach(() => {
4 | jest.clearAllMocks()
5 | })
6 |
7 | describe('util#getJwtToken', () => {
8 | test('correctly returns the JWT token when one is already set', () => {
9 | const token = '123.456.789'
10 |
11 | const event = {
12 | requestContext: {
13 | authorizer: {
14 | jwtToken: token
15 | }
16 | }
17 | }
18 |
19 | expect(getJwtToken(event)).toEqual('123.456.789')
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/serverless/src/util/requestTimeout.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Determine the length of the time to allow an http request to perform
3 | * @param {Integer} workloadThreshold The time to allow the lambda to do its processing beyond the request time
4 | */
5 | export const requestTimeout = (workloadThreshold = 10) => {
6 | const lambdaTimeout = parseInt(process.env.LAMBDA_TIMEOUT, 10)
7 |
8 | // Return the difference between our lambda timeout and the
9 | // time required to perform the lambda work
10 | return (lambdaTimeout - workloadThreshold) * 1000
11 | }
12 |
--------------------------------------------------------------------------------
/portals/amd/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "hasStyles": true,
3 | "logo": {
4 | "id": "amd-logo",
5 | "link": "https://www.scar.org/data-products/antarctic-master-directory/",
6 | "title": "Antarctic Master Directory"
7 | },
8 | "org": "AMD",
9 | "pageTitle": "Antarctic Master Directory",
10 | "parentConfig": "edsc",
11 | "query": {
12 | "tagKey": "org.scar.amd",
13 | "hasGranulesOrCwic": null
14 | },
15 | "ui": {
16 | "showOnlyGranulesCheckbox": false,
17 | "showNonEosdisCheckbox": false
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/static/src/js/util/request/contactInfoRequest.js:
--------------------------------------------------------------------------------
1 | import Request from './request'
2 | import { getEnvironmentConfig } from '../../../../../sharedUtils/config'
3 |
4 | export default class ContactInfoRequest extends Request {
5 | constructor(authToken) {
6 | super(getEnvironmentConfig().apiHost)
7 | this.authenticated = true
8 | this.authToken = authToken
9 | }
10 |
11 | fetch() {
12 | return this.get('contact_info')
13 | }
14 |
15 | updateNotificationLevel(data) {
16 | return this.post('contact_info', data)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/serverless/src/util/echoForms/__tests__/getNameValuePairsForProjections.test.js:
--------------------------------------------------------------------------------
1 | import { loadedEchoFormXml } from './mocks'
2 | import { getNameValuePairsForProjections } from '../getNameValuePairsForProjections'
3 |
4 | describe('util#getNameValuePairsForProjections', () => {
5 | test('correctly discovers the correct fields from the provided xml', () => {
6 | const projectPairs = getNameValuePairsForProjections(loadedEchoFormXml)
7 |
8 | expect(projectPairs).toEqual({
9 | PROJECTION_PARAMETERS: 'Sphere:45,FE:54'
10 | })
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/static/src/js/util/regions.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Prepare parameters used in getRegions() based on current Redux State
3 | * @param {object} state Current Redux State
4 | * @returns Parameters used in buildRegionSearchParams
5 | */
6 | export const prepareRegionParams = (state) => {
7 | const {
8 | query
9 | } = state
10 |
11 | const { region: regionQuery } = query
12 |
13 | const {
14 | endpoint,
15 | exact,
16 | keyword
17 | } = regionQuery
18 |
19 | return {
20 | endpoint,
21 | exact,
22 | query: keyword
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/static/src/js/util/url/arrayEncoders.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Decodes an array (as object) parameter (returns the values of the object)
3 | * @param {Object} object
4 | */
5 | export const decodeArray = (object) => {
6 | if (!object) return undefined
7 |
8 | // If the object is a string, return that
9 | if (typeof object === 'string') return [object]
10 |
11 | return Object.values(object)
12 | }
13 |
14 | /**
15 | * Encodes an array parameter (returns the same value)
16 | * @param {Array} array
17 | */
18 | export const encodeArray = array => array || ''
19 |
--------------------------------------------------------------------------------
/bin/cypress-prepare-travis:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # read in static.config.json
4 | config="`cat static.config.json`"
5 |
6 | # update keys for test Travis CI
7 | config="`jq '.application.ciMode = $newValue' --arg newValue true <<< $config`"
8 |
9 | # overwrite static.config.json with new values
10 | echo $config > tmp.$$.json && mv tmp.$$.json static.config.json
11 |
12 | # create empty overrideStatic.config.json if it doesn't exist
13 | overrideFile=overrideStatic.config.json
14 | if [ ! -f $overrideFile ]; then
15 | echo {} > overrideStatic.config.json
16 | fi
17 |
--------------------------------------------------------------------------------
/portals/suborbital/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "hasStyles": true,
3 | "logo": {
4 | "id": "suborbital-logo",
5 | "link": "https://earthdata.nasa.gov/esds/impact/admg/the-airborne-inventory",
6 | "title": "Sub-Orbital Catalog"
7 | },
8 | "org": "Sub-Orbital Catalog",
9 | "pageTitle": "NASA Sub-Orbital Catalog",
10 | "parentConfig": "edsc",
11 | "query": {
12 | "tagKey": ["gov.nasa.impact.*"],
13 | "hasGranulesOrCwic": null
14 | },
15 | "ui": {
16 | "showOnlyGranulesCheckbox": false,
17 | "showNonEosdisCheckbox": false
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | [
4 | '@babel/preset-env', {
5 | targets: {
6 | node: '8.10',
7 | esmodules: true,
8 | ie: '10'
9 | }
10 | }
11 | ],
12 | '@babel/preset-react'
13 | ],
14 | sourceType: 'unambiguous',
15 | plugins: [
16 | 'react-hot-loader/babel',
17 | '@babel/plugin-proposal-object-rest-spread',
18 | '@babel/plugin-proposal-class-properties',
19 | '@babel/plugin-transform-runtime',
20 | '@babel/plugin-syntax-dynamic-import'
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/static/src/js/components/Well/Well.scss:
--------------------------------------------------------------------------------
1 | .well {
2 | width: 100%;
3 | overflow: hidden;
4 | color: $color__black--800;
5 |
6 | &__main {
7 | padding: $spacer*1.5;
8 | }
9 |
10 | &__heading {
11 | font-weight: 700;
12 | font-size: 1.5rem;
13 | }
14 |
15 | &__introduction {
16 | margin-bottom: 1.5rem;
17 |
18 | a {
19 | color: inherit;
20 | text-decoration: underline;
21 | }
22 | }
23 |
24 | &__section {
25 | margin-bottom: $spacer*1.5;
26 | }
27 |
28 | &__footer {
29 | padding: $spacer*1.5;
30 | }
31 | }
--------------------------------------------------------------------------------
/static/src/js/util/request/loggerRequest.js:
--------------------------------------------------------------------------------
1 | import Request from './request'
2 | import { getEnvironmentConfig } from '../../../../../sharedUtils/config'
3 |
4 | /**
5 | * Request object for logging errors to lambda
6 | */
7 | export default class LoggerRequest extends Request {
8 | constructor() {
9 | super(getEnvironmentConfig().apiHost)
10 | this.lambda = true
11 | }
12 |
13 | log(params) {
14 | return this.post('error_logger', params)
15 | }
16 |
17 | logRelevancy(params) {
18 | return this.post('relevancy_logger', params)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/static/src/js/util/pluralize.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns a pluralized string if a value is greater than zero. It will return a pluralized value for zero as well.
3 | * @param {string} string The string to pluralize.
4 | * @param {number} value The number to use for pluralization.
5 | * @returns {string} A pluralized string.
6 | */
7 | export const pluralize = (string, value) => {
8 | if (typeof value !== 'number') return `${string}`
9 | if (value > 1 || value === 0) {
10 | return `${string}s`
11 | }
12 | return `${string}`
13 | }
14 |
15 | export default pluralize
16 |
--------------------------------------------------------------------------------
/serverless/src/util/echoForms/__tests__/getTopLevelFields.test.js:
--------------------------------------------------------------------------------
1 | import { echoFormXml } from './mocks'
2 | import { getTopLevelFields } from '../getTopLevelFields'
3 |
4 | describe('util#getTopLevelFields', () => {
5 | test('correctly discovers the correct fields from the provided xml', () => {
6 | const topLevelFields = getTopLevelFields(echoFormXml)
7 |
8 | expect(topLevelFields).toEqual({
9 | CLIENT: 'ESI',
10 | FORMAT: 'VRT',
11 | INCLUDE_META: 'false',
12 | REQUEST_MODE: 'async',
13 | SUBAGENT_ID: 'GDAL'
14 | })
15 | })
16 | })
17 |
--------------------------------------------------------------------------------
/static/src/js/components/EDSCModal/EDSCModalOverlay.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | import './EDSCModalOverlay.scss'
5 |
6 | export const EDSCModalOverlay = ({
7 | children
8 | }) => {
9 | if (!children) return null
10 |
11 | return (
12 |
13 | {children}
14 |
15 | )
16 | }
17 |
18 | EDSCModalOverlay.defaultProps = {
19 | children: null
20 | }
21 |
22 | EDSCModalOverlay.propTypes = {
23 | children: PropTypes.node
24 | }
25 |
26 | export default EDSCModalOverlay
27 |
--------------------------------------------------------------------------------
/static/src/js/util/findProvider.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Get full details for a provider from legacy services based on the provided id
3 | * @param {Ojbect} state Current Redux State
4 | * @param {String} id ID of the provider to find (e.g. LPDAAC_ECS)
5 | */
6 | export const findProvider = (state, id) => {
7 | const {
8 | providers
9 | } = state
10 |
11 | return providers.find((element) => {
12 | const { provider } = element
13 | const { provider_id: providerId } = provider
14 |
15 | return providerId === id
16 | })
17 | }
18 |
19 | export default findProvider
20 |
--------------------------------------------------------------------------------
/static/src/js/reducers/focusedGranule.js:
--------------------------------------------------------------------------------
1 | import { UPDATE_FOCUSED_GRANULE, RESTORE_FROM_URL } from '../constants/actionTypes'
2 |
3 | const initialState = ''
4 |
5 | const focusedGranuleReducer = (state = initialState, action) => {
6 | switch (action.type) {
7 | case UPDATE_FOCUSED_GRANULE: {
8 | return action.payload
9 | }
10 | case RESTORE_FROM_URL: {
11 | const { focusedGranule = '' } = action.payload
12 |
13 | return focusedGranule
14 | }
15 | default:
16 | return state
17 | }
18 | }
19 |
20 | export default focusedGranuleReducer
21 |
--------------------------------------------------------------------------------
/serverless/src/util/urs/getEchoToken.js:
--------------------------------------------------------------------------------
1 | import { getAccessTokenFromJwtToken } from './getAccessTokenFromJwtToken'
2 | import { getEdlConfig } from '../configUtil'
3 |
4 | /**
5 | * Returns the Echo-Token header for requests to CMR
6 | * @param {String} jwtToken
7 | */
8 | export const getEchoToken = async (jwtToken) => {
9 | const { access_token: accessToken } = await getAccessTokenFromJwtToken(jwtToken)
10 |
11 | const edlConfig = await getEdlConfig()
12 | const { client } = edlConfig
13 | const { id: clientId } = client
14 |
15 | return `${accessToken}:${clientId}`
16 | }
17 |
--------------------------------------------------------------------------------
/static/src/js/components/SplitBadge/SplitBadge.scss:
--------------------------------------------------------------------------------
1 | .split-badge {
2 | // If the .split-badge__inner is empty, we want to use the normal padding. Otherwise,
3 | // the height of the .split-badge__inner will provide the padding
4 | &:not(&--empty) {
5 | padding: 0;
6 | padding-left: 0.5rem;
7 | }
8 |
9 | &__inner {
10 | display: inline-block;
11 | margin-left: 0.5rem;
12 | padding: 0.3125rem 0.5rem;
13 | background-color: rgba(0,0,0,0.4);
14 | border-top-right-radius: $border-radius;
15 | border-bottom-right-radius: $border-radius;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/static/src/js/events/events.js:
--------------------------------------------------------------------------------
1 | import EventEmitter from 'events'
2 | import $ from 'jquery'
3 |
4 | const emitter = new EventEmitter()
5 |
6 | // Map granules can consume a ton of listeners, so prevent any console errors.
7 | emitter.setMaxListeners(0)
8 |
9 | emitter.on('error.global', (error = true) => {
10 | const { body } = document
11 | if (error) {
12 | $(body).addClass('body--has-error')
13 | } else {
14 | $(body).removeClass('body--has-error')
15 | }
16 | })
17 |
18 | // eslint-disable-next-line import/prefer-default-export
19 | export const eventEmitter = emitter
20 |
--------------------------------------------------------------------------------
/static/src/js/util/collectionMetadata/scienceKeywords.js:
--------------------------------------------------------------------------------
1 | import {
2 | capitalize,
3 | isEqual,
4 | startCase,
5 | uniqWith
6 | } from 'lodash'
7 |
8 | const fixCase = keyword => startCase(capitalize(keyword))
9 |
10 | export const buildScienceKeywords = (json) => {
11 | const { scienceKeywords } = json
12 |
13 | if (!scienceKeywords) return []
14 |
15 | return uniqWith(scienceKeywords.map(keyword => ([
16 | fixCase(keyword.category),
17 | fixCase(keyword.topic),
18 | fixCase(keyword.term)
19 | ])), isEqual)
20 | }
21 |
22 | export default buildScienceKeywords
23 |
--------------------------------------------------------------------------------
/serverless/src/util/echoForms/__tests__/getSubsetDataLayers.test.js:
--------------------------------------------------------------------------------
1 | import { loadedEchoFormXml } from './mocks'
2 | import { getSubsetDataLayers } from '../getSubsetDataLayers'
3 |
4 | describe('util#getSubsetDataLayers', () => {
5 | test('correctly discovers the correct fields from the provided xml', () => {
6 | const subsetDataLayers = getSubsetDataLayers(loadedEchoFormXml)
7 |
8 | expect(subsetDataLayers).toEqual({
9 | SUBSET_DATA_LAYERS: '/MI1B2E/BlueBand,/MI1B2E/BRF Conversion Factors,/MI1B2E/GeometricParameters,/MI1B2E/NIRBand,/MI1B2E/RedBand'
10 | })
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/static/src/js/components/Panels/PanelSection.scss:
--------------------------------------------------------------------------------
1 | .panel-section {
2 | position: absolute;
3 | top: 0;
4 | display: flex;
5 | flex-direction: column;
6 | height: 100%;
7 | width: 100%;
8 | background-color: $color__white;
9 | z-index: 0;
10 | overflow: hidden;
11 | opacity: 0;
12 |
13 | &--is-active {
14 | z-index: 1;
15 | opacity: 1;
16 | }
17 |
18 | > * {
19 | will-change: opacity;
20 | transition: opacity 0.1s ease-out;
21 | opacity: 0;
22 | }
23 |
24 | .is-panels-dragging & {
25 | pointer-events: none;
26 | user-select: none;
27 | }
28 | }
--------------------------------------------------------------------------------
/static/src/js/containers/AppLogoContainer/AppLogoContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { connect } from 'react-redux'
4 |
5 | import AppLogo from '../../components/AppLogo/AppLogo'
6 |
7 | const mapStateToProps = state => ({
8 | portal: state.portal
9 | })
10 |
11 | export const AppLogoContainer = ({
12 | portal
13 | }) => (
14 |
17 | )
18 |
19 | AppLogoContainer.propTypes = {
20 | portal: PropTypes.shape({}).isRequired
21 | }
22 |
23 | export default connect(mapStateToProps)(AppLogoContainer)
24 |
--------------------------------------------------------------------------------
/migrations/1564489093225_remove-environment-default-from-users.js:
--------------------------------------------------------------------------------
1 | exports.shorthands = undefined
2 |
3 | exports.up = (pgm) => {
4 | // We don't want the value provided as the default to be an actual default but we
5 | // need to provide a value for any records that existed before adding this column
6 | pgm.db.query('ALTER TABLE users ALTER COLUMN environment DROP DEFAULT;')
7 | }
8 |
9 | exports.down = (pgm) => {
10 | // Add the default back to prevent issues with node pg migrate matching the column
11 | pgm.db.query('ALTER TABLE users ALTER COLUMN environment SET DEFAULT \'prod\';')
12 | }
13 |
--------------------------------------------------------------------------------
/serverless/src/util/aws/getLambdaConfig.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns an environment specific configuration object for Lambda
3 | * @return {Object} A configuration object for Lambda
4 | */
5 | export const getLambdaConfig = () => {
6 | const productionConfig = {
7 | apiVersion: '2015-03-31'
8 | }
9 |
10 | if (process.env.IS_OFFLINE) {
11 | // The endpoint should be point to the serverless offline host:port
12 | return {
13 | ...productionConfig,
14 | region: 'us-east-1',
15 | endpoint: 'http://localhost:3001'
16 | }
17 | }
18 |
19 | return productionConfig
20 | }
21 |
--------------------------------------------------------------------------------
/static/src/js/actions/__tests__/advancedSearch.test.js:
--------------------------------------------------------------------------------
1 | import { UPDATE_ADVANCED_SEARCH } from '../../constants/actionTypes'
2 | import { updateAdvancedSearch } from '../advancedSearch'
3 |
4 | beforeEach(() => {
5 | jest.clearAllMocks()
6 | })
7 |
8 | describe('updateAdvancedSearch', () => {
9 | test('should create an action to update the advancedSearch state', () => {
10 | const payload = 'authToken-token'
11 | const expectedAction = {
12 | type: UPDATE_ADVANCED_SEARCH,
13 | payload
14 | }
15 | expect(updateAdvancedSearch(payload)).toEqual(expectedAction)
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/static/src/js/reducers/dataQualitySummaries.js:
--------------------------------------------------------------------------------
1 | import { SET_DATA_QUALITY_SUMMARIES } from '../constants/actionTypes'
2 |
3 | const initialState = {}
4 |
5 | const dataQualitySummariesReducer = (state = initialState, action) => {
6 | switch (action.type) {
7 | case SET_DATA_QUALITY_SUMMARIES: {
8 | const { catalogItemId, dataQualitySummaries } = action.payload
9 |
10 | return {
11 | ...state,
12 | [catalogItemId]: dataQualitySummaries
13 | }
14 | }
15 | default:
16 | return state
17 | }
18 | }
19 |
20 | export default dataQualitySummariesReducer
21 |
--------------------------------------------------------------------------------
/static/src/js/reducers/focusedCollection.js:
--------------------------------------------------------------------------------
1 | import { UPDATE_FOCUSED_COLLECTION, RESTORE_FROM_URL } from '../constants/actionTypes'
2 |
3 | const initialState = ''
4 |
5 | const focusedCollectionReducer = (state = initialState, action) => {
6 | switch (action.type) {
7 | case UPDATE_FOCUSED_COLLECTION: {
8 | return action.payload
9 | }
10 | case RESTORE_FROM_URL: {
11 | const { focusedCollection = '' } = action.payload
12 |
13 | return focusedCollection
14 | }
15 | default:
16 | return state
17 | }
18 | }
19 |
20 | export default focusedCollectionReducer
21 |
--------------------------------------------------------------------------------
/static/src/js/util/collectionMetadata/dataCenters.js:
--------------------------------------------------------------------------------
1 | export const buildDataCenters = (json) => {
2 | const { dataCenters } = json
3 |
4 | if (!dataCenters || !dataCenters.length) return undefined
5 |
6 | return dataCenters.map(dataCenter => ({
7 | shortname: `${dataCenter.shortName.toLowerCase() === 'not provided' ? 'Name Not Provided' : dataCenter.shortName}`,
8 | longname: dataCenter.longName,
9 | roles: dataCenter.roles,
10 | contactInformation: dataCenter.contactInformation ? dataCenter.contactInformation : undefined
11 | }))
12 | }
13 |
14 | export default buildDataCenters
15 |
--------------------------------------------------------------------------------
/static/src/js/util/commafy.js:
--------------------------------------------------------------------------------
1 | const commaRegex = new RegExp(/\B(?=(\d{3})+(?!\d))/g)
2 |
3 | /**
4 | * Returns a commafied number.
5 | * @param {number} number The number to commafy.
6 | * @returns {string} A commafied number string.
7 | */
8 | export const commafy = (number) => {
9 | // https://stackoverflow.com/questions/2901102/how-to-print-a-number-with-commas-as-thousands-separators-in-javascript
10 | const num = `${number}`
11 | const parts = num.toString().split('.')
12 | parts[0] = parts[0].replace(commaRegex, ',')
13 | return parts.join('.')
14 | }
15 |
16 | export default commafy
17 |
--------------------------------------------------------------------------------
/static/src/js/components/TemporalDisplay/TemporalSelectionDropdownMenu.scss:
--------------------------------------------------------------------------------
1 | .temporal-selection-dropdown-menu {
2 | width: 27.25rem;
3 | margin-top: -0.5rem;
4 | padding: $spacer;
5 | border: 0;
6 | border-top-left-radius: 0;
7 | border-top-right-radius: 0;
8 | box-shadow: $shadow__panel-dark;
9 |
10 | @include media-breakpoint-up(xl) {
11 | width: 29.25rem;
12 | }
13 |
14 | &__button {
15 | margin-right: 0.5rem;
16 |
17 | &--cancel {
18 | color: $color__black--400;
19 |
20 | &:hover {
21 | color: $color__black--600;
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/static/src/js/reducers/errors.js:
--------------------------------------------------------------------------------
1 | import { ADD_ERROR, REMOVE_ERROR } from '../constants/actionTypes'
2 |
3 | const initialState = []
4 |
5 | const errorsReducer = (state = initialState, action) => {
6 | switch (action.type) {
7 | case ADD_ERROR: {
8 | return [
9 | ...state,
10 | action.payload
11 | ]
12 | }
13 | case REMOVE_ERROR: {
14 | const id = action.payload
15 | const errors = state.filter(error => error.id !== id)
16 |
17 | return errors
18 | }
19 | default:
20 | return state
21 | }
22 | }
23 |
24 | export default errorsReducer
25 |
--------------------------------------------------------------------------------
/migrations/1568146028532_change-users-not-null.js:
--------------------------------------------------------------------------------
1 | exports.shorthands = undefined
2 |
3 | exports.up = (pgm) => {
4 | pgm.db.query('ALTER TABLE users ALTER COLUMN echo_id DROP NOT NULL;')
5 | pgm.db.query('ALTER TABLE users ALTER COLUMN urs_id SET NOT NULL;')
6 | pgm.db.query('ALTER TABLE users ALTER COLUMN environment SET NOT NULL;')
7 | }
8 |
9 | exports.down = (pgm) => {
10 | pgm.db.query('ALTER TABLE users ALTER COLUMN echo_id SET NOT NULL;')
11 | pgm.db.query('ALTER TABLE users ALTER COLUMN urs_id DROP NOT NULL;')
12 | pgm.db.query('ALTER TABLE users ALTER COLUMN environment DROP NOT NULL;')
13 | }
14 |
--------------------------------------------------------------------------------
/serverless/src/adminIsAuthorized/handler.js:
--------------------------------------------------------------------------------
1 | import { getApplicationConfig } from '../../../sharedUtils/config'
2 | /**
3 | * This lambda basically does nothing. The edlAuthorizer is setup on this lambda to determine if the user
4 | * is authorized as an admin. If not, the authorizer will return a 401
5 | */
6 | export default async function adminIsAuthorized() {
7 | const { defaultResponseHeaders } = getApplicationConfig()
8 |
9 | return {
10 | isBase64Encoded: false,
11 | statusCode: 200,
12 | headers: defaultResponseHeaders,
13 | body: JSON.stringify({ authorized: true })
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/serverless/src/submitHarmonyOrder/bboxToPolygon.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Convert a bounding box (2 points) to a polygon (4 points)
3 | * @param {Array} bbox Array of 2 [lng, lat] points
4 | */
5 | export const bboxToPolygon = (bbox) => {
6 | const [swPoint, nePoint] = bbox
7 | const [swLng, swLat] = swPoint
8 | const [neLng, neLat] = nePoint
9 |
10 | // Polygons points need to be in CCW direction (ne, nw, sw, se)
11 | return [[
12 | [neLng, neLat],
13 | [swLng, neLat],
14 | [swLng, swLat],
15 | [neLng, swLat],
16 | [neLng, neLat] // Close the polygon by repeating the first point
17 | ]]
18 | }
19 |
--------------------------------------------------------------------------------
/serverless/src/util/database/getDbConnection.js:
--------------------------------------------------------------------------------
1 | import 'pg'
2 | import knex from 'knex'
3 | import { getDbConnectionConfig } from '../database'
4 |
5 | // Initalize a variable to be set once
6 | let dbConnection
7 |
8 | /**
9 | * Returns a Knex database connection object to the EDSC RDS database
10 | */
11 | export const getDbConnection = async () => {
12 | if (dbConnection == null) {
13 | const dbConnectionConfig = await getDbConnectionConfig()
14 |
15 | dbConnection = knex({
16 | client: 'pg',
17 | connection: dbConnectionConfig
18 | })
19 | }
20 |
21 | return dbConnection
22 | }
23 |
--------------------------------------------------------------------------------
/serverless/src/util/__tests__/createJwtToken.test.js:
--------------------------------------------------------------------------------
1 | import jwt from 'jsonwebtoken'
2 |
3 | import { createJwtToken } from '../createJwtToken'
4 |
5 | describe('util#createJwtToken', () => {
6 | test('correctly returns the JWT token', () => {
7 | const user = {
8 | id: 1,
9 | urs_id: 'testuser',
10 | site_preferences: {}
11 | }
12 |
13 | const result = createJwtToken(user)
14 | const decoded = jwt.decode(result)
15 |
16 | expect(decoded).toEqual(expect.objectContaining({
17 | id: 1,
18 | preferences: {},
19 | username: 'testuser'
20 | }))
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/bin/ecc-sync:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | echo 'deploying'
6 | openssl aes-256-cbc -K $encrypted_461274fed2c2_key -iv $encrypted_461274fed2c2_iv -in .travis/travis.enc -out travis -d
7 |
8 | chmod 600 travis
9 | eval `ssh-agent -s`
10 | ssh-add travis
11 | ssh-keyscan -t rsa -p 7999 git.earthdata.nasa.gov >> ~/.ssh/known_hosts
12 | git remote add ecc ssh://git@git.earthdata.nasa.gov:7999/edsc/earthdata-search-client_repo.git
13 | git fetch ecc "+refs/heads/*:refs/remotes/origin/*"
14 |
15 | git fetch --unshallow || true
16 | echo "TRAVIS_COMMIT=$TRAVIS_COMMIT"
17 | git push ecc $TRAVIS_COMMIT:refs/heads/serverless
18 |
--------------------------------------------------------------------------------
/serverless/src/util/createLimitedShapefile.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Creates a limited shapefile that only contains the shapes that match the provided selectedFeatures
3 | * @param {Object} file Shapefile
4 | * @param {Array} selectedFeatures Array of the selected features of the shapefile
5 | */
6 | export const createLimitedShapefile = (file, selectedFeatures) => {
7 | const newFile = file
8 | const newFeatures = newFile.features.filter((feature) => {
9 | const { edscId } = feature
10 |
11 | return selectedFeatures.includes(edscId)
12 | })
13 |
14 | newFile.features = newFeatures
15 |
16 | return newFile
17 | }
18 |
--------------------------------------------------------------------------------
/static/src/js/components/Sidebar/SidebarSection.scss:
--------------------------------------------------------------------------------
1 | .sidebar-section {
2 | min-height: 0;
3 | margin: 0.5*$spacer;
4 | background-color: $color__black--900;
5 |
6 | &__header {
7 | display: flex;
8 | align-items: center;
9 | justify-content: space-between;
10 | padding: 0.75rem 0.75rem 0.75rem 1.125rem;
11 | color: $color__black--300;
12 | border-bottom: 1px solid $color__black--700;
13 | }
14 |
15 | &__title {
16 | margin: 0;
17 | font-size: 0.875rem;
18 | color: $color__black--400;
19 | }
20 |
21 | &__icon {
22 | color: rgba(255,255,255,0.3)
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/static/src/js/util/alphabetic-list.js:
--------------------------------------------------------------------------------
1 | export const alphabet = ['#', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
2 |
3 | /**
4 | * Returns an object where the keys are the alphbetic characters and the values are empty arrays.
5 | * ie: { #: [], A: [], B: [], ... Z: []}
6 | * @return {object} The formatted object.
7 | */
8 | export const createEmptyAlphabeticListObj = () => {
9 | const alphabeticList = {}
10 |
11 | alphabet.forEach((letter) => {
12 | alphabeticList[letter] = []
13 | })
14 |
15 | return alphabeticList
16 | }
17 |
--------------------------------------------------------------------------------
/static/src/js/components/AccessMethod/AccessMethod.scss:
--------------------------------------------------------------------------------
1 | .access-method {
2 | &__radio-tooltip.fa {
3 | margin-left: $spacer/4;
4 | font-size: 0.875rem;
5 | color: $color__black--400;
6 | }
7 |
8 | &__section-intro,
9 | p.access-method__section-intro {
10 | margin-bottom: $spacer/2
11 | }
12 |
13 | &__section-status {
14 | font-weight: 700;
15 | font-style: italic;
16 | color: $color__black--400;
17 | }
18 |
19 | &__echoform-loading {
20 | height: 10rem;
21 | width: 100%;
22 | display: flex;
23 | align-items: flex-start;
24 | justify-content: flex-start;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/serverless/src/util/echoForms/findFieldElement.js:
--------------------------------------------------------------------------------
1 | import xpath from 'xpath'
2 | import { DOMParser } from 'xmldom'
3 | import { namespaces } from './namespaces'
4 |
5 | /**
6 | * Find a specific tag in the provided XML string
7 | * @param {String} xmlDocument ECHO Form xml as a string
8 | * @param {String} fieldName Name of the tag to look for
9 | * @param {String} dataType XML namespace
10 | */
11 | export const findFieldElement = (xmlDocument, fieldName, dataType = 'ecs') => {
12 | const doc = new DOMParser().parseFromString(xmlDocument)
13 |
14 | return xpath.useNamespaces(namespaces)(`//${dataType}:${fieldName}`, doc)
15 | }
16 |
--------------------------------------------------------------------------------
/sharedUtils/prepareGranuleAccessParams.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Takes granule params and returns the params to be used in a CMR request
3 | * @param {Object} params Queue messages from SQS
4 | * @returns {Object} The granule params
5 | */
6 | export const prepareGranuleAccessParams = (params = {}) => {
7 | const { concept_id: conceptIdsFromParams = [] } = params
8 |
9 | // If there are added granules, return only the added granules. Otherwise, send
10 | // all of the granules params.
11 | if (conceptIdsFromParams.length) {
12 | return {
13 | concept_id: conceptIdsFromParams
14 | }
15 | }
16 |
17 | return params
18 | }
19 |
--------------------------------------------------------------------------------
/static/src/js/util/scrollParent.js:
--------------------------------------------------------------------------------
1 | // more minimal version of https://github.com/olahol/scrollParent.js/blob/master/scrollParent.js
2 | const regex = /(auto|scroll)/
3 |
4 | const style = (node, prop) => getComputedStyle(node, null).getPropertyValue(prop)
5 |
6 | const scroll = node => regex.test(
7 | style(node, 'overflow')
8 | + style(node, 'overflow-y')
9 | + style(node, 'overflow-x')
10 | )
11 |
12 | const scrollParent = (node) => {
13 | if (!node || node === document.body) {
14 | return document.body
15 | }
16 | return scroll(node) ? node : scrollParent(node.parentNode)
17 | }
18 |
19 | export default scrollParent
20 |
--------------------------------------------------------------------------------
/serverless/src/getAccessMethods/supportsVariableSubsetting.js:
--------------------------------------------------------------------------------
1 | import { isEmpty } from 'lodash'
2 |
3 | /**
4 | * Determine whether or not the provided UMM S record supports variable subsetting
5 | * @param {Object} service UMM S record to parse
6 | */
7 | export const supportsVariableSubsetting = (service) => {
8 | const { serviceOptions = {} } = service
9 |
10 | // If there are no service options the record can not support variable subsetting
11 | if (serviceOptions == null) return false
12 |
13 | const { subset = {} } = serviceOptions
14 | const { variableSubset = {} } = subset
15 |
16 | return !isEmpty(variableSubset)
17 | }
18 |
--------------------------------------------------------------------------------
/serverless/src/util/urs/getUserAccessToken.js:
--------------------------------------------------------------------------------
1 | import { getDbConnection } from '../database/getDbConnection'
2 |
3 | /**
4 | * Returns the decrypted urs system credentials from Secrets Manager
5 | */
6 | export const getUserAccessToken = async (id) => {
7 | // Retrieve a connection to the database
8 | const dbConnection = await getDbConnection()
9 |
10 | const existingUserTokens = await dbConnection('user_tokens')
11 | .first([
12 | 'access_token',
13 | 'refresh_token',
14 | 'expires_at'
15 | ])
16 | .where({ user_id: id })
17 | .orderBy('created_at', 'DESC')
18 |
19 | return existingUserTokens
20 | }
21 |
--------------------------------------------------------------------------------
/static/src/js/components/GranuleFilters/GranuleFiltersBody.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | /**
5 | * Renders GranuleFiltersBody.
6 | * @param {Object} props - The props passed into the component.
7 | * @param {Object} props.granuleFiltersForm - The granule filters form.
8 | */
9 | export const GranuleFiltersBody = ({
10 | granuleFiltersForm
11 | }) => (
12 |
13 | {granuleFiltersForm}
14 |
15 | )
16 |
17 | GranuleFiltersBody.propTypes = {
18 | granuleFiltersForm: PropTypes.node.isRequired
19 | }
20 |
21 | export default GranuleFiltersBody
22 |
--------------------------------------------------------------------------------
/static/src/js/components/GranuleFilters/GranuleFiltersList.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | import './GranuleFiltersList.scss'
5 |
6 | /**
7 | * Renders GranuleFiltersList.
8 | * @param {Object} props - The props passed into the component.
9 | * @param {Node} props.children - The granule filter items.
10 | */
11 | export const GranuleFiltersList = ({
12 | children
13 | }) => (
14 |
17 | )
18 |
19 | GranuleFiltersList.propTypes = {
20 | children: PropTypes.node.isRequired
21 | }
22 |
23 | export default GranuleFiltersList
24 |
--------------------------------------------------------------------------------
/static/src/js/util/request/admin/retrievalRequest.js:
--------------------------------------------------------------------------------
1 | import Request from '../request'
2 | import { getEnvironmentConfig } from '../../../../../../sharedUtils/config'
3 |
4 | export default class RetrievalRequest extends Request {
5 | constructor(authToken) {
6 | super(getEnvironmentConfig().apiHost)
7 | this.authenticated = true
8 | this.authToken = authToken
9 | }
10 |
11 | all(params) {
12 | return this.get('admin/retrievals', params)
13 | }
14 |
15 | fetch(id) {
16 | return this.get(`admin/retrievals/${id}`)
17 | }
18 |
19 | isAuthorized() {
20 | return this.get('/admin/is_authorized')
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/serverless/src/util/cmr/prepareExposeHeaders.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns a list of headers to expose to the client response
3 | * @param {Object} headers The headers that were capable of exposing
4 | * @return {Array} The headers to expose, separated by a comma
5 | */
6 | export const prepareExposeHeaders = (headers) => {
7 | // Add 'jwt-token' to access-control-expose-headers, so the client app can read the JWT
8 | const { 'access-control-expose-headers': exposeHeaders = '' } = headers
9 | const exposeHeadersList = exposeHeaders.split(',').filter(Boolean)
10 | exposeHeadersList.push('jwt-token')
11 | return exposeHeadersList.join(', ')
12 | }
13 |
--------------------------------------------------------------------------------
/serverless/src/util/createJwtToken.js:
--------------------------------------------------------------------------------
1 | import jwt from 'jsonwebtoken'
2 | import cmrEnv from '../../../sharedUtils/cmrEnv'
3 | import { getSecretEarthdataConfig } from '../../../sharedUtils/config'
4 |
5 | /**
6 | * Create a signed JWT Token with user information
7 | * @param {Object} user User object from database
8 | */
9 | export const createJwtToken = (user) => {
10 | const {
11 | id,
12 | urs_id: username,
13 | site_preferences: preferences
14 | } = user
15 |
16 | const { secret } = getSecretEarthdataConfig(cmrEnv())
17 |
18 | return jwt.sign({
19 | id,
20 | username,
21 | preferences
22 | }, secret)
23 | }
24 |
--------------------------------------------------------------------------------
/static/src/js/components/Facets/FacetsModalNav.scss:
--------------------------------------------------------------------------------
1 | .facets-modal-nav {
2 | display: flex;
3 | flex-grow: 0;
4 | flex-shrink: 0;
5 | padding: $spacer;
6 | border-bottom: 1px solid #dee2e6;
7 |
8 | &__heading {
9 | margin-right: $spacer/2;
10 | color: $color__black--400;
11 | }
12 |
13 | &__list {
14 | display: flex;
15 | margin: 0;
16 | padding: 0;
17 | list-style: none;
18 | }
19 |
20 | &__entry {
21 | padding: 0 0.125rem;
22 | font-weight: 700;
23 |
24 | &:active {
25 | color: #14669c;
26 | }
27 |
28 | &--inactive {
29 | color: $color__black--400;
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | collectCoverage: true,
3 | collectCoverageFrom: [
4 | 'serverless/src/**/*.js',
5 | 'static/src/**/*.js',
6 | 'sharedUtils/**/*.js'
7 | ],
8 | moduleNameMapper: {
9 | '\\.(jpg|ico|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '/static/src/js/util/mocks/fileMock.js',
10 | '^.+\\.(css|less|scss)$': 'babel-jest'
11 | },
12 | coveragePathIgnorePatterns: [
13 | 'package.json',
14 | 'package-lock.json'
15 | ],
16 | setupFiles: [
17 | '/test-env.js'
18 | ],
19 | testPathIgnorePatterns: [
20 | 'mocks.js'
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | const autoprefixer = require('autoprefixer')
2 | const precss = require('precss')
3 | const cssnano = require('cssnano')
4 |
5 | let plugins = []
6 |
7 | const autoprefix = autoprefixer({
8 | overrideBrowserslist: ['> 1%', 'last 2 versions']
9 | })
10 |
11 |
12 | // In development, we only want autoprefixer. In any other environment,
13 | // we run precss and postcss as well. The order is important here.
14 | if (process.env.NODE_ENV !== 'development') {
15 | plugins = [
16 | precss,
17 | autoprefix,
18 | cssnano
19 | ]
20 | } else {
21 | plugins = [autoprefix]
22 | }
23 |
24 | module.exports = {
25 | plugins
26 | }
27 |
--------------------------------------------------------------------------------
/serverless/src/submitHarmonyOrder/__tests__/ccwShapefile.test.js:
--------------------------------------------------------------------------------
1 | import { ccwShapefile } from '../ccwShapefile'
2 | import {
3 | mockCcwShapefile,
4 | mockCwShapefile,
5 | mockCwShapefileConverted
6 | } from './mocks'
7 |
8 | describe('ccwShapefile', () => {
9 | describe('with a CW shapefile', () => {
10 | test('returns a CCW shapefile', () => {
11 | expect(ccwShapefile(mockCwShapefile)).toEqual(mockCwShapefileConverted)
12 | })
13 | })
14 |
15 | describe('with a CCW shapefile', () => {
16 | test('returns a CCW shapefile', () => {
17 | expect(ccwShapefile(mockCcwShapefile)).toEqual(mockCcwShapefile)
18 | })
19 | })
20 | })
21 |
--------------------------------------------------------------------------------
/static/src/js/actions/panels.js:
--------------------------------------------------------------------------------
1 | import {
2 | PANELS_TOGGLE,
3 | PANELS_SET_PANEL,
4 | PANELS_SET_PANEL_GROUP,
5 | PANELS_SET_PANEL_SECTION
6 | } from '../constants/actionTypes'
7 |
8 |
9 | export const togglePanels = value => ({
10 | type: PANELS_TOGGLE,
11 | payload: value
12 | })
13 |
14 | export const setActivePanel = panelId => ({
15 | type: PANELS_SET_PANEL,
16 | payload: panelId
17 | })
18 |
19 | export const setActivePanelGroup = panelId => ({
20 | type: PANELS_SET_PANEL_GROUP,
21 | payload: panelId
22 | })
23 |
24 | export const setActivePanelSection = panelId => ({
25 | type: PANELS_SET_PANEL_SECTION,
26 | payload: panelId
27 | })
28 |
--------------------------------------------------------------------------------
/static/src/js/components/Panels/PanelGroupHeaderMeta.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | /**
5 | * Renders PanelGroupHeaderMeta.
6 | * @param {Object} props - The props passed into the component.
7 | * @param {String} props.children - Children components.
8 | */
9 | export const PanelGroupHeaderMeta = ({
10 | children
11 | }) => (
12 |
13 | {children}
14 |
15 | )
16 |
17 | PanelGroupHeaderMeta.defaultProps = {
18 | children: null
19 | }
20 |
21 | PanelGroupHeaderMeta.propTypes = {
22 | children: PropTypes.node
23 | }
24 |
25 | export default PanelGroupHeaderMeta
26 |
--------------------------------------------------------------------------------
/static/src/js/components/EDSCTable/EDSCTableCell.scss:
--------------------------------------------------------------------------------
1 | .edsc-table-cell {
2 | flex: 1;
3 | width: 100%;
4 | padding: 0.25rem 0.5rem;
5 | font-size: 0.7rem;
6 | border-top: 1px solid $color__white;
7 | border-bottom: 1px solid $color__black--200;
8 |
9 | .edsc-table__tr--odd & {
10 | border-top: 1px solid darken($color__black--100, 1);
11 | }
12 |
13 | .edsc-table__td--centered & {
14 | display: flex;
15 | justify-content: center;
16 | align-items: center;
17 | text-align: center;
18 | }
19 |
20 | &__content {
21 | white-space: nowrap;
22 | text-overflow: ellipsis;
23 | overflow: hidden;
24 | cursor: text;
25 | }
26 | }
--------------------------------------------------------------------------------
/static/src/js/util/request/__tests__/accessMethodsRequest.test.js:
--------------------------------------------------------------------------------
1 | import AccessMethodsRequest from '../accessMethodsRequest'
2 |
3 | beforeEach(() => {
4 | jest.restoreAllMocks()
5 | jest.clearAllMocks()
6 | })
7 |
8 | describe('AccessMethodsRequest#constructor', () => {
9 | test('sets the default values when authenticated', () => {
10 | const token = '123'
11 | const request = new AccessMethodsRequest(token)
12 |
13 | expect(request.authenticated).toBeTruthy()
14 | expect(request.authToken).toEqual(token)
15 | expect(request.baseUrl).toEqual('http://localhost:3000')
16 | expect(request.searchPath).toEqual('access_methods')
17 | })
18 | })
19 |
--------------------------------------------------------------------------------
/serverless/src/util/chunkArray.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Split an array into an array of smaller arrays
3 | * @param {Array} myArray The array to be split up into chunks
4 | * @param {Number} chunkSize The size of the chunks to split the array into
5 | * @return {Array} An array of arrays split up into the requested sizes
6 | */
7 | export const chunkArray = (myArray, chunkSize) => {
8 | let index = 0
9 | const arrayLength = myArray.length
10 | const tempArray = []
11 |
12 | for (index = 0; index < arrayLength; index += chunkSize) {
13 | const myChunk = myArray.slice(index, index + chunkSize)
14 |
15 | tempArray.push(myChunk)
16 | }
17 |
18 | return tempArray
19 | }
20 |
--------------------------------------------------------------------------------
/serverless/src/util/echoForms/getFieldElementValue.js:
--------------------------------------------------------------------------------
1 | import xpath from 'xpath'
2 | import { DOMParser } from 'xmldom'
3 | import { namespaces } from './namespaces'
4 |
5 | /**
6 | * Find a specific tag in the provided XML string and return its text value
7 | * @param {String} xmlDocument ECHO Form xml as a string
8 | * @param {String} fieldName Name of the tag to look for
9 | * @param {String} dataType XML namespace
10 | */
11 | export const getFieldElementValue = (xmlDocument, fieldName, dataType = 'ecs') => {
12 | const doc = new DOMParser().parseFromString(xmlDocument)
13 |
14 | return xpath.useNamespaces(namespaces)(`string(//${dataType}:${fieldName})`, doc)
15 | }
16 |
--------------------------------------------------------------------------------
/static/src/js/components/ProjectCollections/ProjectCollectionsList.scss:
--------------------------------------------------------------------------------
1 | .project-collections-list {
2 | display: flex;
3 | flex-direction: column;
4 | flex-grow: 1;
5 |
6 | &__list {
7 | overflow: visible;
8 | overflow-y: visible;
9 | margin: 0;
10 | padding: 0;
11 | list-style: none;
12 | }
13 |
14 | &__notice {
15 | margin: 0 auto;
16 | padding: $spacer;
17 | font-size: 0.825rem;
18 | font-style: italic;
19 | text-align: center;
20 | color: $color__black--400;
21 | border-right: 1px solid $border-color;
22 | }
23 |
24 | &__filler {
25 | flex-grow: 1;
26 | border-right: 1px solid $border-color;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/static/src/js/util/isProjectCollectionValid.js:
--------------------------------------------------------------------------------
1 | import { isEmpty } from 'lodash'
2 |
3 | /**
4 | * Returns true if the collection meets all validation requirements
5 | * @param {object} method - The collection access method information.
6 | * @return {boolean}
7 | */
8 | export const isProjectCollectionValid = (method) => {
9 | if (!method) return false
10 |
11 | // Here is where we can check the method to see if its valid
12 | const [methodKey] = Object.keys(method)
13 |
14 | if (methodKey === 'download' && !isEmpty(method)) return true
15 |
16 | const accessMethod = method[methodKey]
17 | const { isValid = false } = accessMethod || {}
18 |
19 | return isValid
20 | }
21 |
--------------------------------------------------------------------------------
/static/src/js/util/request/retrievalRequest.js:
--------------------------------------------------------------------------------
1 | import Request from './request'
2 | import { getEnvironmentConfig } from '../../../../../sharedUtils/config'
3 |
4 | export default class RetrievalRequest extends Request {
5 | constructor(authToken) {
6 | super(getEnvironmentConfig().apiHost)
7 | this.authenticated = true
8 | this.authToken = authToken
9 | }
10 |
11 | all() {
12 | return this.get('retrievals')
13 | }
14 |
15 | remove(id) {
16 | return this.delete(`retrievals/${id}`)
17 | }
18 |
19 | fetch(id) {
20 | return this.get(`retrievals/${id}`)
21 | }
22 |
23 | submit(params) {
24 | return this.post('retrievals', params)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/static/src/js/components/EDSCTable/EDSCTableCell.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | import './EDSCTableCell.scss'
5 |
6 | /**
7 | * Renders EDSCTableCell.
8 | * @param {Object} props - The props passed into the component from react-table.
9 | */
10 | const EDSCTableCell = ({ cell }) => {
11 | const { value } = cell
12 |
13 | return (
14 |
15 |
16 | {value}
17 |
18 |
19 | )
20 | }
21 |
22 | EDSCTableCell.propTypes = {
23 | cell: PropTypes.shape({}).isRequired
24 | }
25 |
26 | export default EDSCTableCell
27 |
--------------------------------------------------------------------------------
/static/src/js/components/OrderDropdownList/OrderDropdownItem.scss:
--------------------------------------------------------------------------------
1 | .order-dropdown-item {
2 | &:not(:last-child) {
3 | margin-bottom: $spacer/2;
4 | }
5 |
6 | &__title {
7 | margin-bottom: $spacer/4;
8 | font-size: 0.75rem;
9 | font-weight: 700;
10 |
11 | span {
12 | font-weight: 400;
13 | }
14 | }
15 |
16 | &__list {
17 | list-style: none;
18 | margin: 0;
19 | padding: 0;
20 | }
21 |
22 | &__list-item {
23 | margin: 0;
24 | line-height: 1.15em;
25 |
26 | &:not(:last-child) {
27 | margin-bottom: $spacer/4;
28 | }
29 | }
30 |
31 | &__link {
32 | font-size: 0.75rem;
33 | white-space: nowrap;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/static/src/js/util/url/__tests__/url.test.js:
--------------------------------------------------------------------------------
1 | import { decodeUrlParams, encodeUrlQuery } from '../url'
2 | import { emptyDecodedResult } from './url.mocks'
3 |
4 | describe('url#decodeUrlParams', () => {
5 | test('given no string it returns no object', () => {
6 | const expectedResult = {
7 | ...emptyDecodedResult
8 | }
9 | expect(decodeUrlParams('')).toEqual(expectedResult)
10 | })
11 | })
12 |
13 | describe('url#encodeUrlQuery', () => {
14 | test('given no query it returns no params', () => {
15 | const props = {
16 | hasGranulesOrCwic: true,
17 | pathname: '/path/here'
18 | }
19 | expect(encodeUrlQuery(props)).toEqual('/path/here')
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/static/src/js/components/TemporalDisplay/TemporalSelectionDropdownToggle.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Dropdown from 'react-bootstrap/Dropdown'
4 |
5 | const TemporalSelectionDropdownToggle = ({ onToggleClick }) => (
6 |
12 |
13 |
14 | )
15 |
16 | TemporalSelectionDropdownToggle.propTypes = {
17 | onToggleClick: PropTypes.func.isRequired
18 | }
19 |
20 | export default TemporalSelectionDropdownToggle
21 |
--------------------------------------------------------------------------------
/serverless/src/getAccessMethods/supportsShapefileSubsetting.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Determine whether or not the provided UMM S record supports shapefile subsetting
3 | * @param {Object} service UMM S record to parse
4 | */
5 | export const supportsShapefileSubsetting = (service) => {
6 | const { serviceOptions = {} } = service
7 |
8 | // If there are no service options the record can not support variable subsetting
9 | if (serviceOptions == null) return false
10 |
11 | const { subset = {} } = serviceOptions
12 | const { spatialSubset = {} } = subset
13 | const {
14 | shapefile = []
15 | } = spatialSubset
16 |
17 | return shapefile.find(shapefile => shapefile.format === 'GeoJSON') != null
18 | }
19 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/static/src/js/components/EDSCTabs/EDSCTabs.scss:
--------------------------------------------------------------------------------
1 |
2 | .edsc-tabs {
3 | &__tabs {
4 | border-bottom: 1px solid $color__black--300;
5 |
6 | .nav-link {
7 | border-radius: 0;
8 | background-color: transparent;
9 | border: 0;
10 | border-bottom: 4px solid transparent;
11 | color: $color__black--500;
12 |
13 | &:hover {
14 | border-bottom: 4px solid $color__black--300;
15 | }
16 | }
17 |
18 | > .nav-link.active {
19 | border: 0;
20 | border-bottom: 4px solid $color__blue--dark;
21 | background-color: transparent;
22 | color: $color__black--700;
23 | }
24 | }
25 |
26 | .tab-content {
27 | padding: $spacer;
28 | }
29 | }
--------------------------------------------------------------------------------
/static/src/js/components/GranuleResults/GranuleResultsBrowseImageCell.scss:
--------------------------------------------------------------------------------
1 | .granule-results-browse-image-cell {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | align-self: flex-end;
6 | padding: 0.25rem;
7 | height: 60px;
8 | width: 60px;
9 | overflow: hidden;
10 | font-size: 0.5rem;
11 | border-bottom: 1px solid $color__black--200;
12 |
13 | &--image {
14 | background-color: #1a1a1a;
15 | border-bottom: 1px solid $color__black--700;
16 | }
17 |
18 | &__thumb {
19 | flex: 1;
20 | height: 100%;
21 | width: 100%;
22 | }
23 |
24 | &__thumb-image {
25 | width: 100%;
26 | height: 100%;
27 | object-fit: contain;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/static/src/js/util/collectionMetadata/doi.js:
--------------------------------------------------------------------------------
1 | export const buildDoi = (json) => {
2 | const { doi } = json
3 |
4 | if (!doi) return undefined
5 |
6 | const doiText = doi.doi
7 | if (!doiText) return { doiLink: undefined, doiText: undefined }
8 |
9 | // This link varies. Clean it up so all links start from the same place.
10 | let doiLink = doiText.replace(/^https?:\/\//g, '')
11 | doiLink = doiLink.replace('doi:', '')
12 | doiLink = doiLink.replace('dx.doi.org/', '')
13 | doiLink = doiLink.replace('doi.org/', '')
14 |
15 | if (doiLink !== '') return { doiLink: `https://dx.doi.org/${doiLink}`, doiText }
16 | return { doiLink: undefined, doiText }
17 | }
18 |
19 | export default buildDoi
20 |
--------------------------------------------------------------------------------
/serverless/src/getAccessMethods/supportsBoundingBoxSubsetting.js:
--------------------------------------------------------------------------------
1 | import { isEmpty } from 'lodash'
2 |
3 | /**
4 | * Determine whether or not the provided UMM S record supports bounding box subsetting
5 | * @param {Object} service UMM S record to parse
6 | */
7 | export const supportsBoundingBoxSubsetting = (service) => {
8 | const { serviceOptions = {} } = service
9 |
10 | // If there are no service options the record can not support variable subsetting
11 | if (serviceOptions == null) return false
12 |
13 | const { subset = {} } = serviceOptions
14 | const { spatialSubset = {} } = subset
15 | const {
16 | boundingBox = {}
17 | } = spatialSubset
18 |
19 | return !isEmpty(boundingBox)
20 | }
21 |
--------------------------------------------------------------------------------
/serverless/src/util/echoForms/__tests__/getFieldElementValue.test.js:
--------------------------------------------------------------------------------
1 | import { echoFormXml } from './mocks'
2 | import { getFieldElementValue } from '../getFieldElementValue'
3 |
4 | describe('util#getFieldElementValue', () => {
5 | test('correctly discovers the correct fields from the provided xml', () => {
6 | expect(getFieldElementValue(echoFormXml, 'CLIENT')).toEqual('ESI')
7 | expect(getFieldElementValue(echoFormXml, 'FORMAT')).toEqual('VRT')
8 | expect(getFieldElementValue(echoFormXml, 'INCLUDE_META')).toEqual('false')
9 | expect(getFieldElementValue(echoFormXml, 'REQUEST_MODE')).toEqual('async')
10 | expect(getFieldElementValue(echoFormXml, 'SUBAGENT_ID')).toEqual('GDAL')
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/static/src/css/modules/_links.scss:
--------------------------------------------------------------------------------
1 | .link {
2 | color: $color__blue--dark;
3 | text-decoration: underline;
4 |
5 | &--separated {
6 | &:after {
7 | display: inline-block;
8 | margin-left: $spacer/2;
9 | content: '|';
10 | color: transparent;
11 | border-left: 1px solid $color__black--300;
12 | }
13 | }
14 |
15 | &--external {
16 | &:after {
17 | display: inline-block;
18 | padding-left: 0.3rem;
19 | content: "\f111";
20 | font-size: 0.6rem;
21 | font-family: "EUI-Icon-Library";
22 | color: $color__black--300;
23 | }
24 |
25 | &:hover {
26 | &:after {
27 | color: $color__black--400;
28 | }
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/static/src/js/util/url/gridEncoders.js:
--------------------------------------------------------------------------------
1 | export const encodeGridCoords = (gridCoords) => {
2 | if (!gridCoords) return ''
3 |
4 | const encodedCoords = gridCoords
5 | .trim()
6 | .replace(/,/g, ':')
7 | .replace(/\s+/g, ',')
8 | .replace(/(^|,)(\d+)($|:)/g, '$1$2-$2$3')
9 | .replace(/(^|:)(\d+)($|,)/g, '$1$2-$2$3')
10 |
11 | return encodedCoords
12 | }
13 |
14 | export const decodeGridCoords = (string) => {
15 | if (!string) return undefined
16 |
17 | const decodedString = string
18 | .replace(/,/g, ' ')
19 | .replace(/:/g, ',')
20 | .replace(/(\d+)-(\d+)/g, (m, m0, m1) => {
21 | if (m0 === m1) return m0
22 | return m
23 | })
24 |
25 | return decodedString
26 | }
27 |
--------------------------------------------------------------------------------
/portals/default/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "features": {
3 | "advancedSearch": false,
4 | "authentication": false,
5 | "featureFacets": {
6 | "showMapImagery": false,
7 | "showNearRealTime": true,
8 | "showCustomizable": false
9 | }
10 | },
11 | "footer": {
12 | "displayVersion": false,
13 | "attributionText": "",
14 | "primaryLinks": [],
15 | "secondaryLinks": []
16 | },
17 | "hasScripts": false,
18 | "hasStyles": false,
19 | "logo": {},
20 | "org": "Site Name",
21 | "pageTitle": "",
22 | "query": {},
23 | "title": "Search",
24 | "ui": {
25 | "showOnlyGranulesCheckbox": false,
26 | "showNonEosdisCheckbox": false,
27 | "showTophat": false
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/static/src/js/components/CollectionDetailsHighlights/skeleton.js:
--------------------------------------------------------------------------------
1 | export const collectionDetailsRow = [
2 | {
3 | shape: 'rectangle',
4 | left: 0,
5 | top: 3,
6 | height: 12,
7 | width: 225,
8 | radius: 2
9 | }
10 | ]
11 |
12 | export const collectionDetailsParagraph = [
13 | {
14 | shape: 'rectangle',
15 | left: 0,
16 | top: 3,
17 | height: 12,
18 | width: 225,
19 | radius: 2
20 | },
21 | {
22 | shape: 'rectangle',
23 | left: 0,
24 | top: 23,
25 | height: 12,
26 | width: 225,
27 | radius: 2
28 | },
29 | {
30 | shape: 'rectangle',
31 | left: 0,
32 | top: 43,
33 | height: 12,
34 | width: 225,
35 | radius: 2
36 | }
37 | ]
38 |
--------------------------------------------------------------------------------
/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example plugins/index.js can be used to load plugins
3 | //
4 | // You can change the location of this file or turn off loading
5 | // the plugins file with the 'pluginsFile' configuration option.
6 | //
7 | // You can read more here:
8 | // https://on.cypress.io/plugins-guide
9 | // ***********************************************************
10 |
11 | // This function is called when a project is opened or re-opened (e.g. due to
12 | // the project's config changing)
13 |
14 | module.exports = (on, config) => {
15 | // `on` is used to hook into various events Cypress emits
16 | // `config` is the resolved Cypress config
17 | }
18 |
--------------------------------------------------------------------------------
/static/src/js/components/ProjectCollections/ProjectCollections.scss:
--------------------------------------------------------------------------------
1 | .project-collections {
2 | position: absolute;
3 | top: 0;
4 | bottom: 0;
5 | left: 0;
6 | right: 0;
7 | display: flex;
8 | flex-direction: column;
9 | flex-grow: 0;
10 | background-color: $color__white;
11 |
12 | &__footer {
13 | flex-shrink: 0;
14 | padding: $spacer/2;
15 | border-top: 1px solid $border-color;
16 | border-right: 1px solid $border-color;
17 | }
18 |
19 | &__footer-message {
20 | margin-bottom: $spacer/2;
21 | font-size: 0.825rem;
22 | color: $color__black--400;
23 | }
24 |
25 | .simplebar-content {
26 | height: 100%;
27 | display: flex;
28 | flex-direction: column;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/static/src/js/reducers/contactInfo.js:
--------------------------------------------------------------------------------
1 | import { UPDATE_CONTACT_INFO } from '../constants/actionTypes'
2 |
3 | const initialState = {}
4 |
5 | const contactInfoReducer = (state = initialState, action) => {
6 | switch (action.type) {
7 | case UPDATE_CONTACT_INFO: {
8 | const { echoPreferences, ursProfile } = action.payload
9 |
10 | return {
11 | ...state,
12 | echoPreferences: {
13 | ...state.echoPreferences,
14 | ...echoPreferences
15 | },
16 | ursProfile: {
17 | ...state.ursProfile,
18 | ...ursProfile
19 | }
20 | }
21 | }
22 | default:
23 | return state
24 | }
25 | }
26 |
27 | export default contactInfoReducer
28 |
--------------------------------------------------------------------------------
/static/src/js/components/Panels/PanelGroupFooter.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | import './PanelGroupFooter.scss'
5 |
6 |
7 | /**
8 | * Renders PanelGroupFooter.
9 | * @param {object} props - The props passed into the component.
10 | * @param {node} props.footer - The element to be used as the footer. Can be overridden on an individual PanelItem.
11 | */
12 | export const PanelGroupFooter = ({ footer }) => (
13 |
16 | )
17 |
18 | PanelGroupFooter.defaultProps = {
19 | footer: null
20 | }
21 |
22 | PanelGroupFooter.propTypes = {
23 | footer: PropTypes.node
24 | }
25 |
26 | export default PanelGroupFooter
27 |
--------------------------------------------------------------------------------
/sharedUtils/portalPath.js:
--------------------------------------------------------------------------------
1 | import { isDefaultPortal } from '../static/src/js/util/portals'
2 |
3 | /**
4 | * Provides a prefix for links that takes the active portal into account
5 | * @param {Object} portal Object with a portalId key
6 | */
7 | export const portalPath = (portal) => {
8 | if (!portal) return ''
9 | const { portalId = '' } = portal
10 | let portalPath = ''
11 | if (!isDefaultPortal(portalId)) portalPath = `/portal/${portalId}`
12 |
13 | return portalPath
14 | }
15 |
16 | /**
17 | * Wrapper for portalPath() that takes the full Redux state
18 | * @param {Object} state Redux state
19 | */
20 | export const portalPathFromState = (state) => {
21 | const { portal } = state
22 | return portalPath(portal)
23 | }
24 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '12.16.3'
4 | addons:
5 | apt:
6 | packages:
7 | # Ubuntu 16+ does not install this dependency by default, so we need to install it ourselves
8 | - libgconf-2-4
9 | cache:
10 | npm: true
11 | directories:
12 | # we also need to cache folder with Cypress binary
13 | - ~/.cache
14 | script:
15 | - npm install
16 | - npm run copy-secrets
17 | - npm run silent-test
18 | - npm run cypress:prepare-travis
19 | - npm run cypress:ci
20 | branches:
21 | only: # Only build master. All pull requests also get built
22 | - master
23 | deploy:
24 | provider: script
25 | script: bin/ecc-sync
26 | on:
27 | branch: master
28 | repo: nasa/earthdata-search
29 |
--------------------------------------------------------------------------------
/migrations/1559966487087_projects.js:
--------------------------------------------------------------------------------
1 | exports.shorthands = undefined
2 |
3 | exports.up = (pgm) => {
4 | pgm.createTable('projects', {
5 | id: 'id',
6 | name: {
7 | type: 'varchar(1000)'
8 | },
9 | path: {
10 | type: 'text',
11 | notNull: true
12 | },
13 | user_id: {
14 | type: 'integer',
15 | references: 'users'
16 | },
17 | updated_at: {
18 | type: 'timestamp',
19 | notNull: true,
20 | default: pgm.func('current_timestamp')
21 | },
22 | created_at: {
23 | type: 'timestamp',
24 | notNull: true,
25 | default: pgm.func('current_timestamp')
26 | }
27 | })
28 | }
29 |
30 | exports.down = (pgm) => {
31 | pgm.dropTable('projects')
32 | }
33 |
--------------------------------------------------------------------------------
/serverless/src/util/pick.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Select only desired keys from a provided object.
3 | * @param {object} providedObj - An object containing any keys.
4 | * @param {array} keys - An array of strings that represent the keys to be picked.
5 | * @return {obj} An object containing only the desired keys.
6 | */
7 | export const pick = (providedObj = {}, keys) => {
8 | let obj = null
9 |
10 | // if `null` is provided the default parameter will not be
11 | // set so we'll handle it manually
12 | if (providedObj == null) {
13 | obj = {}
14 | } else {
15 | obj = providedObj
16 | }
17 |
18 | Object.keys(obj).forEach((k) => {
19 | if (!keys.includes(k)) {
20 | delete obj[k]
21 | }
22 | })
23 |
24 | return obj
25 | }
26 |
--------------------------------------------------------------------------------
/static/src/js/util/__tests__/isLinkType.test.js:
--------------------------------------------------------------------------------
1 | import { isLinkType } from '../isLinkType'
2 |
3 | describe('isLinkType', () => {
4 | describe('when passed an link that matches', () => {
5 | test('returns true', () => {
6 | expect(isLinkType('http://www.link.com/link/testvalue#', 'testvalue')).toEqual(true)
7 | })
8 | })
9 |
10 | describe('when passed an link does not match', () => {
11 | test('returns false', () => {
12 | expect(isLinkType('http://www.link.com/link/anothertest#', 'testvalue')).toEqual(false)
13 | })
14 | })
15 |
16 | describe('when passed an undefined link', () => {
17 | test('returns false', () => {
18 | expect(isLinkType(undefined, 'testvalue')).toEqual(false)
19 | })
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/static/src/js/actions/admin/isAuthorized.js:
--------------------------------------------------------------------------------
1 | import RetrievalRequest from '../../util/request/admin/retrievalRequest'
2 | import { SET_ADMIN_IS_AUTHORIZED } from '../../constants/actionTypes'
3 |
4 | export const updateIsAuthorized = isAuthorized => ({
5 | type: SET_ADMIN_IS_AUTHORIZED,
6 | payload: isAuthorized
7 | })
8 |
9 | export const adminIsAuthorized = () => (dispatch, getState) => {
10 | const { authToken } = getState()
11 |
12 | const requestObject = new RetrievalRequest(authToken)
13 | const response = requestObject.isAuthorized()
14 | .then(() => {
15 | // If the user is not authorized, the 401 will be caught before getting to this code.
16 | dispatch(updateIsAuthorized(true))
17 | })
18 |
19 | return response
20 | }
21 |
--------------------------------------------------------------------------------
/static/src/js/components/FormFields/Radio/Radio.scss:
--------------------------------------------------------------------------------
1 | .radio {
2 | display: flex;
3 | align-items: center;
4 | justify-content: flex-start;
5 | padding: $spacer/2 $spacer;
6 | width: 100%;
7 | border: 1px solid $color__black--200;
8 | border-radius: 0.125rem;
9 | vertical-align: middle;
10 |
11 | &--is-selected {
12 | background-color: $color__black--100;
13 | }
14 |
15 | &__input {
16 | display: inline-block;
17 | vertical-align: middle;
18 | }
19 |
20 | &__content {
21 | display: inline-block;
22 | margin-left: $spacer/2;
23 | font-weight: 700;
24 | color: $color__black--500;
25 | vertical-align: middle;
26 |
27 | .radio--is-selected & {
28 | color: $color__black--600;
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/sharedUtils/outputFormatMaps.js:
--------------------------------------------------------------------------------
1 | /**
2 | * OUS expects that output formats be provided in the form of file
3 | * extensions, this is a map between UMM-S values and their respective extensions
4 | */
5 | export const ousFormatMapping = {
6 | 'NETCDF-3': 'nc',
7 | 'NETCDF-4': 'nc4',
8 | BINARY: 'dods',
9 | ASCII: 'ascii'
10 | }
11 |
12 | /**
13 | * Harmony expects that output formats be provided in the form of mimetypes,
14 | * this is a map between UMM-S values and their respective mimetypes
15 | */
16 | export const harmonyFormatMapping = {
17 | 'NETCDF-4': 'application/x-netcdf4',
18 | GEOTIFF: 'image/tiff',
19 | GIF: 'image/gif',
20 | PNG: 'image/png',
21 | 'Shapefile+zip': 'application/shapefile+zip',
22 | ZARR: 'application/x-zarr'
23 | }
24 |
--------------------------------------------------------------------------------
/static/src/css/vendor/bootstrap/_components.scss:
--------------------------------------------------------------------------------
1 | /*
2 | * Bootstrap components
3 | */
4 |
5 | @import "~bootstrap/scss/_alert";
6 | @import "~bootstrap/scss/_badge";
7 | @import "~bootstrap/scss/_buttons";
8 | @import "~bootstrap/scss/_breadcrumb";
9 | @import "~bootstrap/scss/_card";
10 | @import "~bootstrap/scss/_close";
11 | @import "~bootstrap/scss/_code";
12 | @import "~bootstrap/scss/_dropdown";
13 | @import "~bootstrap/scss/_input-group";
14 | @import "~bootstrap/scss/_modal";
15 | @import "~bootstrap/scss/_nav";
16 | @import "~bootstrap/scss/_popover";
17 | @import "~bootstrap/scss/_progress";
18 | @import "~bootstrap/scss/_spinners";
19 | @import "~bootstrap/scss/_tables";
20 | @import "~bootstrap/scss/_tooltip";
21 | @import "~bootstrap/scss/_transitions";
22 |
--------------------------------------------------------------------------------
/static/src/js/reducers/__tests__/authToken.test.js:
--------------------------------------------------------------------------------
1 | import authTokenReducer from '../authToken'
2 | import { UPDATE_AUTH } from '../../constants/actionTypes'
3 |
4 | describe('INITIAL_STATE', () => {
5 | test('is correct', () => {
6 | const action = { type: 'dummy_action' }
7 | const initialState = ''
8 |
9 | expect(authTokenReducer(undefined, action)).toEqual(initialState)
10 | })
11 | })
12 |
13 | describe('UPDATE_AUTH', () => {
14 | test('returns the correct state', () => {
15 | const token = 'authToken-token'
16 | const action = {
17 | type: UPDATE_AUTH,
18 | payload: token
19 | }
20 |
21 | const expectedState = token
22 |
23 | expect(authTokenReducer(undefined, action)).toEqual(expectedState)
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/cypress/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
--------------------------------------------------------------------------------
/static/src/js/components/Sidebar/Sidebar.scss:
--------------------------------------------------------------------------------
1 | .sidebar {
2 | position: relative;
3 | height: 100%;
4 | display: flex;
5 | flex-direction: column;
6 | flex-shrink: 0;
7 | flex-grow: 0;
8 | width: 17.5rem;
9 | z-index: 3;
10 |
11 | &__inner {
12 | position: relative;
13 | display: flex;
14 | flex-direction: column;
15 | flex-grow: 1;
16 | height: 100%;
17 | z-index: 1;
18 | background: $color__black--800;
19 | }
20 |
21 | &__section-title {
22 | color: $color__white;
23 | }
24 |
25 | &--hidden {
26 | display: none;
27 | }
28 |
29 | &__content {
30 | min-height: 0;
31 | flex: 0 1 100%;
32 | }
33 |
34 | .simplebar-scrollbar:before {
35 | background-color: $color__black--300;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/static/src/js/util/__tests__/portals.test.js:
--------------------------------------------------------------------------------
1 | import { isDefaultPortal } from '../portals'
2 | import * as getApplicationConfig from '../../../../../sharedUtils/config'
3 |
4 | beforeEach(() => {
5 | jest.clearAllMocks()
6 | jest.restoreAllMocks()
7 | })
8 |
9 | describe('isDefaultPortal', () => {
10 | beforeEach(() => {
11 | jest.spyOn(getApplicationConfig, 'getApplicationConfig').mockImplementation(() => ({
12 | defaultPortal: 'edsc'
13 | }))
14 | })
15 |
16 | test('returns true if the portalId matches the defaultPortal', () => {
17 | expect(isDefaultPortal('edsc')).toBeTruthy()
18 | })
19 |
20 | test('returns false if the portalId does not match the defaultPortal', () => {
21 | expect(isDefaultPortal('simple')).toBeFalsy()
22 | })
23 | })
24 |
--------------------------------------------------------------------------------
/secret.config.json.example:
--------------------------------------------------------------------------------
1 | {
2 | "environment": {
3 | "development": {
4 | "dbUsername": "",
5 | "dbPassword": ""
6 | },
7 | "production": {}
8 | },
9 | "earthdata": {
10 | "sit": {
11 | "clientId": "URS_CLIENT_ID",
12 | "password": "URS_PASSWORD",
13 | "secret": "JWT_SIGNING_SECRET_KEY"
14 | },
15 | "uat": {
16 | "clientId": "URS_CLIENT_ID",
17 | "password": "URS_PASSWORD",
18 | "secret": "JWT_SIGNING_SECRET_KEY"
19 | },
20 | "prod": {
21 | "clientId": "URS_CLIENT_ID",
22 | "password": "URS_PASSWORD",
23 | "secret": "JWT_SIGNING_SECRET_KEY"
24 | }
25 | },
26 | "cypress": {
27 | "user": {
28 | "id": 1,
29 | "username": "your username here"
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/static/src/js/components/FilterStack/FilterStack.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import classNames from 'classnames'
4 |
5 | import './FilterStack.scss'
6 |
7 | const FilterStack = (props) => {
8 | const {
9 | children,
10 | isOpen
11 | } = props
12 |
13 | if (!children) return null
14 |
15 | const className = classNames({
16 | 'filter-stack': true,
17 | 'filter-stack--is-open': isOpen
18 | })
19 |
20 | return (
21 |
24 | )
25 | }
26 |
27 | FilterStack.defaultProps = {
28 | children: null
29 | }
30 |
31 | FilterStack.propTypes = {
32 | children: PropTypes.node,
33 | isOpen: PropTypes.bool.isRequired
34 | }
35 |
36 | export default FilterStack
37 |
--------------------------------------------------------------------------------
/static/src/js/components/CollectionResults/CollectionResultsBody.scss:
--------------------------------------------------------------------------------
1 | .collection-results-body {
2 | height: 100%;
3 | width: 100%;
4 | position: relative;
5 |
6 | &__view-enter {
7 | display: block;
8 | visibility: visible;
9 | opacity: 0;
10 | }
11 |
12 | &__view-enter-active {
13 | opacity: 1;
14 | visibility: visible;
15 | transition: opacity 200ms;
16 | }
17 |
18 | &__view-enter-done {
19 | display: block;
20 | visibility: visible;
21 | opacity: 1;
22 | }
23 |
24 | &__view-exit {
25 | opacity: 1;
26 | visibility: visible;
27 | }
28 |
29 | &__view-exit-active {
30 | opacity: 0;
31 | transition: opacity 200ms;
32 | }
33 |
34 | &__view-exit-done {
35 | opacity: 0;
36 | visibility: hidden;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/static/src/js/util/url/__tests__/buildAuthenticatedRedirectUrl.test.js:
--------------------------------------------------------------------------------
1 | import { buildAuthenticatedRedirectUrl } from '../buildAuthenticatedRedirectUrl'
2 |
3 | describe('url#buildAuthenticatedRedirectUrl', () => {
4 | test('builds a url when no query params are provided', () => {
5 | expect(buildAuthenticatedRedirectUrl('http://edsc-test.com', 'abc.123.def'))
6 | .toEqual('http://localhost:3000/concepts/metadata?url=http://edsc-test.com&token=abc.123.def')
7 | })
8 |
9 | test('builds a url when provided query params', () => {
10 | expect(buildAuthenticatedRedirectUrl('http://edsc-test.com?some_param=string_value', 'abc.123.def'))
11 | .toEqual('http://localhost:3000/concepts/metadata?url=http://edsc-test.com?some_param=string_value&token=abc.123.def')
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/serverless/src/util/echoForms/getSwitchFields.js:
--------------------------------------------------------------------------------
1 | import { getFieldElementValue } from './getFieldElementValue'
2 |
3 | const booleanTranslations = {
4 | true: 'Y',
5 | True: 'Y',
6 | TRUE: 'Y',
7 | y: 'Y',
8 | Y: 'Y',
9 | false: 'N',
10 | False: 'N',
11 | FALSE: 'N',
12 | n: 'N',
13 | N: 'N'
14 | }
15 |
16 | /**
17 | * Get boolean data from the provided XML Document
18 | * @param {String} xmlDocument ECHO Form xml as a string
19 | */
20 | export const getSwitchFields = (xmlDocument) => {
21 | const switchFields = ['INCLUDE_META']
22 |
23 | const switchFieldValues = {}
24 |
25 | switchFields.forEach((field) => {
26 | switchFieldValues[field] = booleanTranslations[getFieldElementValue(xmlDocument, field).trim()]
27 | })
28 |
29 | return switchFieldValues
30 | }
31 |
--------------------------------------------------------------------------------
/static/src/js/selectors/__tests__/granuleMetadata.test.js:
--------------------------------------------------------------------------------
1 | import { getFocusedGranuleMetadata } from '../granuleMetadata'
2 |
3 | describe('getFocusedGranuleMetadata selector', () => {
4 | test('returns the granule metadata', () => {
5 | const state = {
6 | focusedGranule: 'granuleId',
7 | metadata: {
8 | granules: {
9 | granuleId: {
10 | mock: 'data'
11 | }
12 | }
13 | }
14 | }
15 |
16 | expect(getFocusedGranuleMetadata(state)).toEqual({ mock: 'data' })
17 | })
18 |
19 | test('returns an empty object when there is no focusedGranule', () => {
20 | const state = {
21 | focusedGranule: '',
22 | metadata: {}
23 | }
24 |
25 | expect(getFocusedGranuleMetadata(state)).toEqual({})
26 | })
27 | })
28 |
--------------------------------------------------------------------------------
/migrations/1556745445952_colormaps.js:
--------------------------------------------------------------------------------
1 | exports.shorthands = undefined
2 |
3 | exports.up = (pgm) => {
4 | pgm.createTable('colormaps', {
5 | id: 'id',
6 | product: {
7 | type: 'varchar(1000)',
8 | notNull: true
9 | },
10 | url: {
11 | type: 'varchar(1000)',
12 | notNull: true
13 | },
14 | jsondata: {
15 | type: 'jsonb',
16 | notNull: true,
17 | default: '{}'
18 | },
19 | updated_at: {
20 | type: 'timestamp',
21 | notNull: true,
22 | default: pgm.func('current_timestamp')
23 | },
24 | created_at: {
25 | type: 'timestamp',
26 | notNull: true,
27 | default: pgm.func('current_timestamp')
28 | }
29 | })
30 | }
31 |
32 | exports.down = (pgm) => {
33 | pgm.dropTable('colormaps')
34 | }
35 |
--------------------------------------------------------------------------------
/serverless/src/relevancyLogger/handler.js:
--------------------------------------------------------------------------------
1 | import { getApplicationConfig } from '../../../sharedUtils/config'
2 | /**
3 | * Logs an error reported by a client
4 | * @param {Object} event Details about the HTTP request that it received
5 | */
6 | const relevancyLogger = async (event) => {
7 | const { defaultResponseHeaders } = getApplicationConfig()
8 |
9 | const { body } = event
10 | const { params = {} } = JSON.parse(body)
11 | const { data = {} } = params
12 |
13 | const eventData = {
14 | event: 'collection_relevancy',
15 | ...data,
16 | timestamp: Date.now()
17 | }
18 |
19 | console.log(`[metrics] ${JSON.stringify(eventData)}`)
20 |
21 | return {
22 | statusCode: 200,
23 | headers: defaultResponseHeaders
24 | }
25 | }
26 |
27 | export default relevancyLogger
28 |
--------------------------------------------------------------------------------
/static/src/js/reducers/admin/__tests__/isAuthorized.test.js:
--------------------------------------------------------------------------------
1 | import { SET_ADMIN_IS_AUTHORIZED } from '../../../constants/actionTypes'
2 | import adminIsAuthorizedReducer from '../isAuthorized'
3 |
4 | describe('INITIAL_STATE', () => {
5 | test('is correct', () => {
6 | const action = { type: 'dummy_action' }
7 | const expectedState = false
8 |
9 | expect(adminIsAuthorizedReducer(undefined, action)).toEqual(expectedState)
10 | })
11 | })
12 |
13 | describe('SET_ADMIN_IS_AUTHORIZED', () => {
14 | test('returns the correct state', () => {
15 | const action = {
16 | type: SET_ADMIN_IS_AUTHORIZED,
17 | payload: true
18 | }
19 |
20 | const expectedState = true
21 |
22 | expect(adminIsAuthorizedReducer(undefined, action)).toEqual(expectedState)
23 | })
24 | })
25 |
--------------------------------------------------------------------------------
/static/src/js/util/getPanelSizeMap.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @typedef {Object} PanelSizeMap
3 | * @property {Boolean} xs - Boolean flag for size 'xs'
4 | * @property {Boolean} sm - Boolean flag for size 'sm'
5 | * @property {Boolean} md - Boolean flag for size 'md'
6 | * @property {Boolean} lg - Boolean flag for size 'lg'
7 | * @property {Boolean} xl - Boolean flag for size 'xl'
8 | */
9 |
10 | /**
11 | * Given the width of the resizable overlay panel in pixels, returns a
12 | * map to determine the current size of the panel
13 | * @param {Number} width The width of the panel.
14 | * @returns {PanelSizeMap} A map of panel sizes.
15 | */
16 | export const getPanelSizeMap = width => ({
17 | xs: true,
18 | sm: width >= 500,
19 | md: width >= 700,
20 | lg: width >= 900,
21 | xl: width >= 1100
22 | })
23 |
--------------------------------------------------------------------------------
/serverless/src/util/urs/getAccessTokenFromJwtToken.js:
--------------------------------------------------------------------------------
1 | import { getDbConnection } from '../database/getDbConnection'
2 | import { getVerifiedJwtToken } from '../getVerifiedJwtToken'
3 |
4 | /**
5 | * Returns the decrypted urs system credentials from Secrets Manager
6 | */
7 | export const getAccessTokenFromJwtToken = async (jwtToken) => {
8 | // Retrieve a connection to the database
9 | const dbConnection = await getDbConnection()
10 |
11 | const { id } = getVerifiedJwtToken(jwtToken)
12 |
13 | const existingUserTokens = await dbConnection('user_tokens')
14 | .first([
15 | 'access_token',
16 | 'refresh_token',
17 | 'expires_at',
18 | 'user_id'
19 | ])
20 | .where({ user_id: id })
21 | .orderBy('created_at', 'DESC')
22 |
23 | return existingUserTokens
24 | }
25 |
--------------------------------------------------------------------------------
/static/src/js/util/__tests__/limitDecimalPoints.test.js:
--------------------------------------------------------------------------------
1 | import { limitDecimalPoints, limitLatLngDecimalPoints } from '../limitDecimalPoints'
2 |
3 | describe('limitDecimalPoints', () => {
4 | test('limits points to 5 decimal places', () => {
5 | const latLng = ['12.123456789', '34.123456789']
6 | const expectedResult = [12.12346, 34.12346]
7 |
8 | expect(limitDecimalPoints(latLng)).toEqual(expectedResult)
9 | })
10 | })
11 |
12 | describe('limitLatLngDecimalPoints', () => {
13 | test('limits array of points to 5 decimal places', () => {
14 | const latLngs = ['12.123456789,34.123456789', '56.123456789,78.123456789']
15 | const expectedResult = ['12.12346,34.12346', '56.12346,78.12346']
16 |
17 | expect(limitLatLngDecimalPoints(latLngs)).toEqual(expectedResult)
18 | })
19 | })
20 |
--------------------------------------------------------------------------------
/static/src/js/util/url/__tests__/focusedGranuleUrl.test.js:
--------------------------------------------------------------------------------
1 | import { decodeUrlParams, encodeUrlQuery } from '../url'
2 |
3 | import { emptyDecodedResult } from './url.mocks'
4 |
5 | describe('url#decodeUrlParams', () => {
6 | test('decodes focusedGranule correctly', () => {
7 | const expectedResult = {
8 | ...emptyDecodedResult,
9 | focusedGranule: 'granuleId'
10 | }
11 | expect(decodeUrlParams('?g=granuleId')).toEqual(expectedResult)
12 | })
13 | })
14 |
15 | describe('url#encodeUrlQuery', () => {
16 | test('encodes focusedGranule correctly', () => {
17 | const props = {
18 | hasGranulesOrCwic: true,
19 | pathname: '/path/here',
20 | focusedGranule: 'granuleId'
21 | }
22 | expect(encodeUrlQuery(props)).toEqual('/path/here?g=granuleId')
23 | })
24 | })
25 |
--------------------------------------------------------------------------------
/serverless/src/util/cmr/cmrUrl.js:
--------------------------------------------------------------------------------
1 | import { stringify } from 'qs'
2 | import { getEarthdataConfig } from '../../../../sharedUtils/config'
3 | import { cmrEnv } from '../../../../sharedUtils/cmrEnv'
4 |
5 | /**
6 | * Construct a CMR url provided a path and query params
7 | * @param {String} path The path component of the url to be generated
8 | * @param {Object} queryParams An object that will represent the query parameters in the url
9 | * @return {String} A completed and valid url to CMR
10 | */
11 | export const cmrUrl = (path, queryParams = {}) => {
12 | const baseUrl = `${getEarthdataConfig(cmrEnv()).cmrHost}/${path}`
13 |
14 | if (Object.keys(queryParams).length) {
15 | return `${baseUrl}?${stringify(queryParams, { indices: false, arrayFormat: 'brackets' })}`
16 | }
17 |
18 | return baseUrl
19 | }
20 |
--------------------------------------------------------------------------------
/static/src/js/selectors/granuleMetadata.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect'
2 |
3 | import { getFocusedGranuleId } from './focusedGranule'
4 |
5 | /**
6 | * Retrieve all granule metadata from Redux
7 | * @param {Object} state Current state of Redux
8 | */
9 | export const getGranulesMetadata = (state) => {
10 | const { metadata = {} } = state
11 | const { granules = {} } = metadata
12 |
13 | return granules
14 | }
15 |
16 | /**
17 | * Retrieve metadata from Redux pertaining to the focused granule id
18 | */
19 | export const getFocusedGranuleMetadata = createSelector(
20 | [getFocusedGranuleId, getGranulesMetadata],
21 | (focusedGranuleId, granulesMetadata) => {
22 | const { [focusedGranuleId]: granuleMetadata = {} } = granulesMetadata
23 |
24 | return granuleMetadata
25 | }
26 | )
27 |
--------------------------------------------------------------------------------
/static/src/js/util/url/facetEncoders.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Encodes a Facet object into a string
3 | * @param {object} facets Facet object
4 | * @return {string} A `!` delimited string of the facet values
5 | */
6 | export const encodeFacets = (facets) => {
7 | if (!facets) return ''
8 |
9 | const encoded = []
10 |
11 | facets.forEach((facet) => {
12 | encoded.push(facet)
13 | })
14 |
15 | return encoded.join('!')
16 | }
17 |
18 |
19 | /**
20 | * Decodes a Facet parameter string into an object
21 | * @param {string} string A `!` delimited string of the facet values
22 | * @return {object} Facet object
23 | */
24 | export const decodeFacets = (string) => {
25 | if (!string) {
26 | return undefined
27 | }
28 |
29 | const decodedValues = string.split('!')
30 |
31 | return decodedValues
32 | }
33 |
--------------------------------------------------------------------------------
/static/src/js/util/getActivePanelSize.js:
--------------------------------------------------------------------------------
1 | import { getPanelSizeMap } from './getPanelSizeMap'
2 |
3 | /**
4 | * Given the width of the resizable overlay panel in pixels, returns a
5 | * a string with the current size.
6 | * @param {Number} width The width of the panel.
7 | * @returns {(String|Null)} A string representing the current size, or null.
8 | */
9 | export const getActivePanelSize = (width) => {
10 | const panelSizeObj = getPanelSizeMap(width)
11 |
12 | switch (true) {
13 | case panelSizeObj.xl:
14 | return 'xl'
15 | case panelSizeObj.lg:
16 | return 'lg'
17 | case panelSizeObj.md:
18 | return 'md'
19 | case panelSizeObj.sm:
20 | return 'sm'
21 | case panelSizeObj.xs:
22 | return 'xs'
23 | default:
24 | break
25 | }
26 |
27 | return null
28 | }
29 |
--------------------------------------------------------------------------------
/static/src/js/selectors/__tests__/collectionMetadata.test.js:
--------------------------------------------------------------------------------
1 | import { getFocusedCollectionMetadata } from '../collectionMetadata'
2 |
3 | describe('getFocusedCollectionMetadata selector', () => {
4 | test('returns the collection metadata', () => {
5 | const state = {
6 | focusedCollection: 'collectionId',
7 | metadata: {
8 | collections: {
9 | collectionId: {
10 | mock: 'data'
11 | }
12 | }
13 | }
14 | }
15 | expect(getFocusedCollectionMetadata(state)).toEqual({ mock: 'data' })
16 | })
17 |
18 | test('returns an empty object when there is no focusedCollection', () => {
19 | const state = {
20 | focusedCollection: '',
21 | metadata: {}
22 | }
23 |
24 | expect(getFocusedCollectionMetadata(state)).toEqual({})
25 | })
26 | })
27 |
--------------------------------------------------------------------------------
/static/src/js/util/collectionMetadata/__tests__/granuleLimit.test.js:
--------------------------------------------------------------------------------
1 | import { getGranuleLimit } from '../granuleLimit'
2 |
3 | describe('granuleLimit', () => {
4 | test('returns the granule limit if it exists', () => {
5 | const metadata = {
6 | tags: {
7 | 'edsc.limited_collections': {
8 | data: {
9 | limit: 100
10 | }
11 | }
12 | }
13 | }
14 | expect(getGranuleLimit(metadata)).toEqual(100)
15 | })
16 |
17 | test('returns the app default if the tag does not exist', () => {
18 | const metadata = {
19 | tags: {}
20 | }
21 | expect(getGranuleLimit(metadata)).toEqual(1000000)
22 | })
23 |
24 | test('returns the app default if the metadata is empty', () => {
25 | expect(getGranuleLimit(undefined)).toEqual(1000000)
26 | })
27 | })
28 |
--------------------------------------------------------------------------------
/static.webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | const merge = require('webpack-merge')
2 | const WebpackBar = require('webpackbar')
3 |
4 | const StaticCommonConfig = require('./static.webpack.config.common')
5 |
6 | const Config = merge.smartStrategy(
7 | {
8 | devtool: 'replace',
9 | 'module.rules.use': 'prepend'
10 | }
11 | )(StaticCommonConfig, {
12 | mode: 'development',
13 | devtool: 'inline-cheap-module-source-map',
14 | devServer: {
15 | historyApiFallback: true
16 | },
17 | module: {
18 | rules: [
19 | {
20 | test: /\.(css|scss)$/,
21 | exclude: /portals/i,
22 | use: [
23 | {
24 | loader: 'style-loader'
25 | }
26 | ]
27 | }
28 | ]
29 | },
30 | plugins: [
31 | new WebpackBar()
32 | ]
33 | })
34 |
35 | module.exports = Config
36 |
--------------------------------------------------------------------------------
/static/src/js/components/ProjectPanels/ProjectPanels.scss:
--------------------------------------------------------------------------------
1 | .project-panels {
2 | &__footer {
3 | display: flex;
4 | flex-direction: row;
5 | justify-content: flex-end;
6 | align-items: center;
7 | vertical-align: middle;
8 | padding: $spacer/2 $spacer;
9 | }
10 |
11 | &__action {
12 | margin-left: $spacer/2;
13 | }
14 |
15 | &__collection-status {
16 | display: inline-block;
17 | margin-right: $spacer/2;
18 | font-size: 1.125rem;
19 | vertical-align: middle;
20 |
21 | &--valid {
22 | color: $color__green;
23 | }
24 |
25 | &--invalid {
26 | color: $color__red;
27 | }
28 | }
29 |
30 | &__collection-count {
31 | display: inline-block;
32 | margin-right: $spacer/2;
33 | color: $color__black--400;
34 | vertical-align: middle;
35 | }
36 | }
--------------------------------------------------------------------------------
/static/src/js/util/pathStartsWith.js:
--------------------------------------------------------------------------------
1 | import { castArray } from 'lodash'
2 |
3 | /**
4 | * Returns true if the pathname starts with one of the paths
5 | * @param {string} pathname - The pathname to check.
6 | * @param {string|array} paths - The path(s) to check against.
7 | * @return {boolean}
8 | */
9 | export const pathStartsWith = (pathname, paths) => castArray(paths).some((path = '') => {
10 | const pathnameWithoutTrailingSlash = pathname.replace(/\/+$/, '')
11 | const pathWithoutTrailingSlash = path.replace(/\/+$/, '')
12 |
13 | const pathnameWithoutPortal = pathnameWithoutTrailingSlash.replace(/portal\/[^/]*\//, '')
14 |
15 | return pathnameWithoutTrailingSlash.startsWith(pathWithoutTrailingSlash)
16 | || pathnameWithoutPortal.startsWith(pathWithoutTrailingSlash)
17 | })
18 |
19 | export default pathStartsWith
20 |
--------------------------------------------------------------------------------
/static/src/js/util/request/projectRequest.js:
--------------------------------------------------------------------------------
1 | import Request from './request'
2 | import { getEnvironmentConfig } from '../../../../../sharedUtils/config'
3 |
4 | export default class ProjectRequest extends Request {
5 | constructor(authToken) {
6 | super(getEnvironmentConfig().apiHost)
7 | this.lambda = true
8 |
9 | if (authToken && authToken.length > 0) {
10 | this.authenticated = true
11 | this.authToken = authToken
12 | }
13 | }
14 |
15 | all() {
16 | this.authenticated = true
17 | return this.get('projects')
18 | }
19 |
20 | save(params) {
21 | return this.post('projects', params)
22 | }
23 |
24 | fetch(projectId) {
25 | return this.get(`projects/${projectId}`)
26 | }
27 |
28 | remove(projectId) {
29 | return this.delete(`projects/${projectId}`)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/static/src/js/components/AdminRetrievals/__tests__/AdminRetrievalForm.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Enzyme, { shallow } from 'enzyme'
3 | import Adapter from 'enzyme-adapter-react-16'
4 |
5 | import { Form } from 'react-bootstrap'
6 | import { AdminRetrievalsForm } from '../AdminRetrievalsForm'
7 |
8 | Enzyme.configure({ adapter: new Adapter() })
9 |
10 | function setup() {
11 | const props = {
12 | onAdminViewRetrieval: jest.fn()
13 | }
14 |
15 | const enzymeWrapper = shallow()
16 |
17 | return {
18 | enzymeWrapper,
19 | props
20 | }
21 | }
22 |
23 | describe('AdminRetrievals component', () => {
24 | test('renders itself correctly', () => {
25 | const { enzymeWrapper } = setup()
26 |
27 | expect(enzymeWrapper.find(Form).length).toBe(1)
28 | })
29 | })
30 |
--------------------------------------------------------------------------------
/static/src/css/modules/_dropdown.scss:
--------------------------------------------------------------------------------
1 | .dropdown-menu {
2 | padding: .25rem 0;
3 |
4 | &--carat-right {
5 | @include carat('topright');
6 | left: 0.45rem !important;
7 | }
8 | }
9 |
10 | .dropdown-header {
11 | font-weight: 700;
12 | color: $color__black--500;
13 |
14 | .dropdown-menu--condensed & {
15 | padding: .25rem .75rem;
16 | padding-bottom: 0;
17 | font-size: 0.625rem;
18 | }
19 | }
20 |
21 | .dropdown-item {
22 | color: $color__black--800;
23 | font-size: 0.875rem;
24 |
25 | &:hover {
26 | background-color: lighten($color__black--200, 5%);
27 | color: $color__black--700;
28 | }
29 |
30 | .dropdown-menu--condensed & {
31 | padding: .25rem .75rem;
32 | font-size: 0.75rem;
33 | }
34 | }
35 |
36 | .dropdown-toggle {
37 | &:after {
38 | vertical-align: middle;
39 | }
40 | }
--------------------------------------------------------------------------------
/static/src/js/components/GranuleFilters/__tests__/GranuleFiltersHeader.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Enzyme, { shallow } from 'enzyme'
3 | import Adapter from 'enzyme-adapter-react-16'
4 |
5 | import GranuleFiltersHeader from '../GranuleFiltersHeader'
6 |
7 | Enzyme.configure({ adapter: new Adapter() })
8 |
9 | function setup() {
10 | const props = {
11 | title: 'Collection ID'
12 | }
13 |
14 | const enzymeWrapper = shallow()
15 |
16 | return {
17 | enzymeWrapper,
18 | props
19 | }
20 | }
21 |
22 | describe('GranuleFiltersHeader component', () => {
23 | test('sets the dataset ID correctly', () => {
24 | const { enzymeWrapper } = setup()
25 |
26 | expect(enzymeWrapper.find('.granule-filters-header__secondary').text()).toEqual('Collection ID')
27 | })
28 | })
29 |
--------------------------------------------------------------------------------
/static/src/js/components/Panels/PanelSection.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import classNames from 'classnames'
4 |
5 | import './PanelSection.scss'
6 |
7 | export const PanelSection = ({ children, isActive, isOpen }) => {
8 | const className = classNames([
9 | 'panel-section',
10 | {
11 | 'panel-section--is-open': isOpen,
12 | 'panel-section--is-active': isActive
13 | }
14 | ])
15 | return (
16 |
19 | )
20 | }
21 |
22 | PanelSection.defaultProps = {
23 | isActive: false,
24 | isOpen: false
25 | }
26 |
27 |
28 | PanelSection.propTypes = {
29 | children: PropTypes.node.isRequired,
30 | isOpen: PropTypes.bool,
31 | isActive: PropTypes.bool
32 | }
33 |
34 | export default PanelSection
35 |
--------------------------------------------------------------------------------
/static/src/js/util/__tests__/pluralize.test.js:
--------------------------------------------------------------------------------
1 | import { pluralize } from '../pluralize'
2 |
3 | describe('util#pluralize', () => {
4 | describe('when given no value', () => {
5 | test('returns the correct string', () => {
6 | expect(pluralize('test')).toEqual('test')
7 | })
8 | })
9 | describe('when given value of 0', () => {
10 | test('returns the correct string', () => {
11 | expect(pluralize('test', 0)).toEqual('tests')
12 | })
13 | })
14 | describe('when given value of 1', () => {
15 | test('returns the correct string', () => {
16 | expect(pluralize('test', 1)).toEqual('test')
17 | })
18 | })
19 | describe('when given value greater than 1', () => {
20 | test('returns the correct string', () => {
21 | expect(pluralize('test', 2)).toEqual('tests')
22 | })
23 | })
24 | })
25 |
--------------------------------------------------------------------------------
/static/src/js/util/url/buildAuthenticatedRedirectUrl.js:
--------------------------------------------------------------------------------
1 | import { stringify } from 'qs'
2 | import { getEnvironmentConfig } from '../../../../../sharedUtils/config'
3 |
4 | /**
5 | * Construct a url to a lambda that is responsible for redirecting the user with an appropriate cmr token
6 | * @param {String} url The url to be redirected to after a token has been retrieved
7 | * @param {Object} params An object containing all the query parameters
8 | * @param {String} jwtToken JWT Token that the lambda will use to lookup a user token
9 | */
10 | export const buildAuthenticatedRedirectUrl = (url, jwtToken) => {
11 | const { apiHost } = getEnvironmentConfig()
12 |
13 | const queryString = stringify({
14 | url,
15 | token: jwtToken
16 | }, { encode: false })
17 |
18 | return `${apiHost}/concepts/metadata?${queryString}`
19 | }
20 |
--------------------------------------------------------------------------------
/migrations/1563477325342_access-configurations.js:
--------------------------------------------------------------------------------
1 | exports.shorthands = undefined
2 |
3 | exports.up = (pgm) => {
4 | pgm.createTable('access_configurations', {
5 | id: 'id',
6 | user_id: {
7 | type: 'integer',
8 | references: 'users'
9 | },
10 | collection_id: {
11 | type: 'varchar(1000)',
12 | notNull: true
13 | },
14 | access_method: {
15 | type: 'jsonb',
16 | notNull: true,
17 | default: '{}'
18 | },
19 | updated_at: {
20 | type: 'timestamp',
21 | notNull: true,
22 | default: pgm.func('current_timestamp')
23 | },
24 | created_at: {
25 | type: 'timestamp',
26 | notNull: true,
27 | default: pgm.func('current_timestamp')
28 | }
29 | })
30 | }
31 |
32 | exports.down = (pgm) => {
33 | pgm.dropTable('access_configurations')
34 | }
35 |
--------------------------------------------------------------------------------
/static/src/js/reducers/__tests__/browser.test.js:
--------------------------------------------------------------------------------
1 | import browserReducer from '../browser'
2 | import {
3 | UPDATE_BROWSER_VERSION
4 | } from '../../constants/actionTypes'
5 |
6 | const initialState = {}
7 |
8 | describe('INITIAL_STATE', () => {
9 | test('is correct', () => {
10 | const action = { type: 'dummy_action' }
11 |
12 | expect(browserReducer(undefined, action)).toEqual(initialState)
13 | })
14 | })
15 |
16 | describe('UPDATE_BROWSER_VERSION', () => {
17 | test('returns the correct state', () => {
18 | const action = {
19 | type: UPDATE_BROWSER_VERSION,
20 | payload: {
21 | name: 'some browser name'
22 | }
23 | }
24 |
25 | const expectedState = {
26 | name: 'some browser name'
27 | }
28 |
29 | expect(browserReducer(undefined, action)).toEqual(expectedState)
30 | })
31 | })
32 |
--------------------------------------------------------------------------------
/static/src/js/util/__tests__/getFilenameFromPath.test.js:
--------------------------------------------------------------------------------
1 | import { getFilenameFromPath } from '../getFilenameFromPath'
2 |
3 | describe('#getFilenameFromPath', () => {
4 | describe('when passed no path', () => {
5 | test('should return an empty string', () => {
6 | const result = getFilenameFromPath()
7 | expect(result).toEqual('')
8 | })
9 | })
10 |
11 | describe('when passed an empty string', () => {
12 | test('should return an empty string', () => {
13 | const result = getFilenameFromPath('')
14 | expect(result).toEqual('')
15 | })
16 | })
17 |
18 | describe('when passed an valid filepath', () => {
19 | test('should return the filepath', () => {
20 | const result = getFilenameFromPath('http://www.testing.com/this/file.txt')
21 | expect(result).toEqual('file.txt')
22 | })
23 | })
24 | })
25 |
--------------------------------------------------------------------------------
/static/src/js/util/url/__tests__/shapefileId.test.js:
--------------------------------------------------------------------------------
1 | import { decodeUrlParams, encodeUrlQuery } from '../url'
2 |
3 | import { emptyDecodedResult } from './url.mocks'
4 |
5 | describe('url#decodeUrlParams', () => {
6 | test('decodes shapefileId correctly', () => {
7 | const expectedResult = {
8 | ...emptyDecodedResult,
9 | shapefile: {
10 | ...emptyDecodedResult.shapefile,
11 | shapefileId: '123'
12 | }
13 | }
14 | expect(decodeUrlParams('?sf=123')).toEqual(expectedResult)
15 | })
16 | })
17 |
18 | describe('url#encodeUrlQuery', () => {
19 | test('encodes shapefileId correctly', () => {
20 | const props = {
21 | hasGranulesOrCwic: true,
22 | pathname: '/path/here',
23 | shapefileId: '123'
24 | }
25 | expect(encodeUrlQuery(props)).toEqual('/path/here?sf=123')
26 | })
27 | })
28 |
--------------------------------------------------------------------------------
/static/src/js/components/Sidebar/SidebarSection.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 | import React from 'react'
3 |
4 | import './SidebarSection.scss'
5 |
6 | const SidebarSection = (props) => {
7 | const {
8 | children,
9 | sectionTitle
10 | } = props
11 |
12 | return (
13 |
14 | {
15 | sectionTitle && (
16 |
19 | )
20 | }
21 | {children}
22 |
23 | )
24 | }
25 |
26 | SidebarSection.defaultProps = {
27 | sectionTitle: null
28 | }
29 |
30 | SidebarSection.propTypes = {
31 | sectionTitle: PropTypes.string,
32 | children: PropTypes.node.isRequired
33 | }
34 |
35 | export default SidebarSection
36 |
--------------------------------------------------------------------------------
/static/src/js/selectors/collectionMetadata.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect'
2 |
3 | import { getFocusedCollectionId } from './focusedCollection'
4 |
5 | /**
6 | * Retrieve all collection metadata from Redux
7 | * @param {Object} state Current state of Redux
8 | */
9 | export const getCollectionsMetadata = (state) => {
10 | const { metadata = {} } = state
11 | const { collections = {} } = metadata
12 |
13 | return collections
14 | }
15 |
16 | /**
17 | * Retrieve metadata from Redux pertaining to the focused collection id
18 | */
19 | export const getFocusedCollectionMetadata = createSelector(
20 | [getFocusedCollectionId, getCollectionsMetadata],
21 | (focusedCollectionId, collectionsMetadata) => {
22 | const { [focusedCollectionId]: collectionMetadata = {} } = collectionsMetadata
23 |
24 | return collectionMetadata
25 | }
26 | )
27 |
--------------------------------------------------------------------------------
/static/src/js/util/limitDecimalPoints.js:
--------------------------------------------------------------------------------
1 | import { getApplicationConfig } from '../../../../sharedUtils/config'
2 |
3 | /**
4 | * Limits a Latitude/Longitude point to a configured number of decimal places
5 | * @param {Array} latLng A Lat/Lng point as an array
6 | */
7 | export const limitDecimalPoints = (latLng) => {
8 | const { defaultSpatialDecimalSize } = getApplicationConfig()
9 | return latLng.map(point => Number(parseFloat(point).toFixed(defaultSpatialDecimalSize)))
10 | }
11 |
12 | /**
13 | * Limits an array of Latitude/Longitude points to a configured number of decimal places
14 | * @param {Array} latLngs Array of Lat/Lng points (each point a comma delimited string)
15 | */
16 | export const limitLatLngDecimalPoints = latLngs => latLngs.map((latLng) => {
17 | const points = latLng.split(',')
18 | return limitDecimalPoints(points).join(',')
19 | })
20 |
--------------------------------------------------------------------------------
/static/src/partials/analytics.html:
--------------------------------------------------------------------------------
1 | <% if (gtmPropertyId.length > 0) { %>
2 |
21 | <% } %>
22 |
--------------------------------------------------------------------------------
/static/src/js/components/CollectionDetails/CollectionDetailsHeader.scss:
--------------------------------------------------------------------------------
1 | .collection-details-header {
2 | &__primary {
3 | padding: 0.5rem 1rem;
4 | }
5 |
6 | &__title {
7 | display: inline;
8 | margin-right: 0.75rem;
9 | font-weight: 700;
10 | font-size: 1rem;
11 | vertical-align: middle;
12 | }
13 |
14 | &__title-link {
15 | position: relative;
16 | display: inline;
17 | white-space: nowrap;
18 | font-size: 0.875rem;
19 | vertical-align: middle;
20 | }
21 |
22 | &__short-name {
23 | font-size: 0.625rem;
24 | margin-right: $spacer/2;
25 |
26 | .panels--sm & {
27 | font-size: 0.6875rem;
28 | }
29 | }
30 |
31 | &__version-id {
32 | font-size: 0.625rem;
33 | background-color: $color__purple;
34 |
35 | .panels--sm & {
36 | font-size: 0.6875rem;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/static/src/js/components/OrderProgressList/OrderProgressList.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | import OrderProgressItem from './OrderProgressItem'
5 |
6 | import './OrderProgressList.scss'
7 |
8 | export const OrderProgressList = ({
9 | orders
10 | }) => (
11 |
12 | {
13 | orders.map((order) => {
14 | const { order_number: orderNumber } = order
15 |
16 | if (orderNumber == null) return null
17 |
18 | return (
19 |
23 | )
24 | })
25 | }
26 |
27 | )
28 |
29 | OrderProgressList.propTypes = {
30 | orders: PropTypes.arrayOf(
31 | PropTypes.shape({})
32 | ).isRequired
33 | }
34 |
35 | export default OrderProgressList
36 |
--------------------------------------------------------------------------------
/static/src/js/components/Dropzone/ShapefileDropzone.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { PropTypes } from 'prop-types'
3 |
4 | import classNames from 'classnames'
5 |
6 | import withDropzone from './withDropzone'
7 | import './ShapefileDropzone.scss'
8 |
9 | export class ShapefileDropzone extends Component {
10 | constructor(props) {
11 | super(props)
12 | this.ref = React.createRef()
13 | }
14 |
15 | render() {
16 | const { className } = this.props
17 | const classes = classNames([
18 | 'shapefile-dropzone',
19 | className
20 | ])
21 |
22 | return (
23 |
27 | )
28 | }
29 | }
30 |
31 | ShapefileDropzone.propTypes = {
32 | className: PropTypes.string.isRequired
33 | }
34 |
35 | export default withDropzone(ShapefileDropzone)
36 |
--------------------------------------------------------------------------------
/serverless/src/util/echoForms/getNameValuePairsForResample.js:
--------------------------------------------------------------------------------
1 | import { getFieldElementValue } from './getFieldElementValue'
2 |
3 | /**
4 | * Get resampling parameters from the provided XML Document
5 | * @param {String} xmlDocument ECHO Form xml as a string
6 | */
7 | export const getNameValuePairsForResample = (xmlDocument) => {
8 | const resampleFields = ['RESAMPLE']
9 |
10 | const resampleFieldValues = {}
11 |
12 | resampleFields.forEach((fieldName) => {
13 | const valueField = getFieldElementValue(xmlDocument, `${fieldName}/*[contains(local-name(),'value')]`)
14 | const dimensionField = getFieldElementValue(xmlDocument, `${fieldName}/*[contains(local-name(),'dimension')]`)
15 |
16 | if (dimensionField && valueField) {
17 | resampleFieldValues[fieldName] = `${dimensionField}:${valueField}`
18 | }
19 | })
20 |
21 | return resampleFieldValues
22 | }
23 |
--------------------------------------------------------------------------------
/sharedUtils/prepKeysForCmr.js:
--------------------------------------------------------------------------------
1 | import { stringify } from 'qs'
2 |
3 | /**
4 | * Create a query string containing both indexed and non-indexed keys.
5 | * @param {object} queryParams - An object containing all queryParams.
6 | * @param {array} nonIndexedKeys - An array of strings that represent the keys which should not be indexed.
7 | * @return {string} A query string containing both indexed and non-indexed keys.
8 | */
9 | export const prepKeysForCmr = (queryParams, nonIndexedKeys = []) => {
10 | const nonIndexedAttrs = {}
11 | const indexedAttrs = { ...queryParams }
12 |
13 | nonIndexedKeys.forEach((key) => {
14 | nonIndexedAttrs[key] = indexedAttrs[key]
15 | delete indexedAttrs[key]
16 | })
17 |
18 | return [
19 | stringify(indexedAttrs),
20 | stringify(nonIndexedAttrs, { indices: false, arrayFormat: 'brackets' })
21 | ].filter(Boolean).join('&')
22 | }
23 |
--------------------------------------------------------------------------------
/static/src/js/components/SpatialDisplay/__tests__/SpatialDisplayEntry.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Enzyme, { shallow } from 'enzyme'
3 | import Adapter from 'enzyme-adapter-react-16'
4 | import SpatialDisplayEntry from '../SpatialDisplayEntry'
5 |
6 | Enzyme.configure({ adapter: new Adapter() })
7 |
8 | function setup() {
9 | const props = {
10 | type: 'start'
11 | }
12 |
13 | const enzymeWrapper = shallow(
14 |
15 | 38.79165, -77.11976
16 |
17 | )
18 |
19 | return {
20 | enzymeWrapper,
21 | props
22 | }
23 | }
24 |
25 | describe('SpatialDisplayEntry component', () => {
26 | test('with valid type and children should render correctly', () => {
27 | const { enzymeWrapper } = setup()
28 |
29 | expect(enzymeWrapper.text()).toEqual('38.79165, -77.11976')
30 | })
31 | })
32 |
--------------------------------------------------------------------------------
/static/src/js/util/url/__tests__/granuleDataFormatFacetUrl.test.js:
--------------------------------------------------------------------------------
1 | import { decodeUrlParams, encodeUrlQuery } from '../url'
2 |
3 | import { emptyDecodedResult } from './url.mocks'
4 |
5 | describe('url#decodeUrlParams', () => {
6 | test('decodes granuleDataFormatFacets correctly', () => {
7 | const expectedResult = {
8 | ...emptyDecodedResult,
9 | cmrFacets: {
10 | granule_data_format_h: ['Binary']
11 | }
12 | }
13 | expect(decodeUrlParams('?gdf=Binary')).toEqual(expectedResult)
14 | })
15 | })
16 |
17 | describe('url#encodeUrlQuery', () => {
18 | test('encodes granuleDataFormatFacets correctly', () => {
19 | const props = {
20 | hasGranulesOrCwic: true,
21 | pathname: '/path/here',
22 | granuleDataFormatFacets: ['Binary']
23 | }
24 | expect(encodeUrlQuery(props)).toEqual('/path/here?gdf=Binary')
25 | })
26 | })
27 |
--------------------------------------------------------------------------------
/static/src/js/util/url/__tests__/projectFacetUrl.test.js:
--------------------------------------------------------------------------------
1 | import { decodeUrlParams, encodeUrlQuery } from '../url'
2 |
3 | import { emptyDecodedResult } from './url.mocks'
4 |
5 | describe('url#decodeUrlParams', () => {
6 | test('decodes projectFacets correctly', () => {
7 | const expectedResult = {
8 | ...emptyDecodedResult,
9 | cmrFacets: {
10 | project_h: ['facet 1', 'facet 2']
11 | }
12 | }
13 | expect(decodeUrlParams('?fpj=facet%201!facet%202')).toEqual(expectedResult)
14 | })
15 | })
16 |
17 | describe('url#encodeUrlQuery', () => {
18 | test('encodes projectFacets correctly', () => {
19 | const props = {
20 | hasGranulesOrCwic: true,
21 | pathname: '/path/here',
22 | projectFacets: ['facet 1', 'facet 2']
23 | }
24 | expect(encodeUrlQuery(props)).toEqual('/path/here?fpj=facet%201!facet%202')
25 | })
26 | })
27 |
--------------------------------------------------------------------------------
/serverless/src/util/__tests__/pick.test.js:
--------------------------------------------------------------------------------
1 | import { pick } from '../pick'
2 |
3 | beforeEach(() => {
4 | jest.clearAllMocks()
5 | })
6 |
7 | describe('util#pick', () => {
8 | test('correctly returns when null is provided', () => {
9 | const data = pick(null, ['a'])
10 |
11 | expect(Object.keys(data).sort()).toEqual([])
12 | })
13 |
14 | test('correctly returns when undefined is provided', () => {
15 | const data = pick(undefined, ['a'])
16 |
17 | expect(Object.keys(data).sort()).toEqual([])
18 | })
19 |
20 | test('correctly picks whitelisted keys', () => {
21 | const object = {
22 | a: 1,
23 | b: 2,
24 | array: [1, 2, 3],
25 | obj: { c: 3 }
26 | }
27 | const desiredKeys = ['array', 'b']
28 |
29 | const data = pick(object, desiredKeys)
30 |
31 | expect(Object.keys(data).sort()).toEqual(['array', 'b'])
32 | })
33 | })
34 |
--------------------------------------------------------------------------------
/static/src/js/util/itemToRowColumnIndicies.js:
--------------------------------------------------------------------------------
1 | import { isNumber } from 'lodash'
2 |
3 | /**
4 | * @typedef {Object} Indicies
5 | * @property {Number} rowIndex - The X Coordinate
6 | * @property {Number} columnIndex - The Y Coordinate
7 | */
8 |
9 | /**
10 | * Given the zero-based index in a list and a number of columns, returns an object
11 | * containing the zero-based row and column indicies of the item.
12 | * @param {Number} index The number to commafy.
13 | * @param {Number} numColumns The number to commafy.
14 | * @returns {Indicies} The item location.
15 | */
16 | export const itemToRowColumnIndicies = (index, numColumns) => {
17 | if (!isNumber(index) || !isNumber(numColumns)) return undefined
18 |
19 | const rowIndex = Math.floor(index / numColumns)
20 | const columnIndex = index % numColumns
21 |
22 | return {
23 | rowIndex,
24 | columnIndex
25 | }
26 | }
27 |
--------------------------------------------------------------------------------