├── .eslintignore
├── .eslintrc
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── custom.md
│ └── feature_request.md
├── pull_request_template.md
└── workflows
│ └── ci.yml
├── .gitignore
├── .nvmrc
├── .stylelintignore
├── .stylelintrc.json
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── babel.config.json
├── bin
├── deploy-bamboo.sh
├── download_schemas.sh
├── start-env.js
└── start.js
├── index.html
├── jsconfig.json
├── package-lock.json
├── package.json
├── serverless-configs
├── aws-cors-configuration.yml
├── aws-functions.yml
├── aws-infrastructure-resources.yml
└── aws-resources.yml
├── serverless-infrastructure.yml
├── serverless.yml
├── serverless
└── src
│ ├── createTemplate
│ ├── __tests__
│ │ └── handler.test.js
│ └── handler.js
│ ├── deleteTemplate
│ ├── __tests__
│ │ └── handler.test.js
│ └── handler.js
│ ├── edlAuthorizer
│ ├── __tests__
│ │ └── handler.test.js
│ └── handler.js
│ ├── errorLogger
│ ├── __tests__
│ │ └── handler.test.js
│ └── handler.js
│ ├── getEdlUsers
│ ├── __tests__
│ │ └── handler.test.js
│ └── handler.js
│ ├── getTemplate
│ ├── __tests__
│ │ └── handler.test.js
│ └── handler.js
│ ├── getTemplates
│ ├── __tests__
│ │ └── handler.test.js
│ └── handler.js
│ ├── gkrKeywordRecommendations
│ ├── __mocks__
│ │ └── gkrKeywordRecommendationsResponse.js
│ ├── __tests__
│ │ └── handler.test.js
│ └── handler.js
│ ├── gkrSendFeedback
│ ├── __mocks__
│ │ └── gkrSendFeedbackResponse.js
│ ├── __tests__
│ │ └── handler.test.js
│ └── handler.js
│ ├── samlCallback
│ ├── __tests__
│ │ └── handler.test.js
│ └── handler.js
│ ├── samlLogin
│ ├── __tests__
│ │ └── handler.test.js
│ └── handler.js
│ ├── samlRefreshToken
│ ├── __tests__
│ │ └── handler.test.js
│ └── handler.js
│ ├── updateTemplate
│ ├── __tests__
│ │ └── handler.test.js
│ └── handler.js
│ └── utils
│ ├── __tests__
│ ├── createCookie.test.js
│ ├── createJwt.test.js
│ ├── downcaseKeys.test.js
│ ├── fetchEdlClientToken.test.js
│ ├── fetchEdlProfile.test.js
│ ├── getS3Client.test.js
│ └── s3ListObjects.test.js
│ ├── authorizer
│ ├── __tests__
│ │ └── generatePolicy.test.js
│ └── generatePolicy.js
│ ├── createCookie.js
│ ├── createJwt.js
│ ├── downcaseKeys.js
│ ├── fetchEdlClientToken.js
│ ├── fetchEdlProfile.js
│ ├── getS3Client.js
│ └── s3ListObjects.js
├── setup
├── data
│ ├── collection01.json
│ ├── collection02.json
│ ├── collection03.json
│ ├── collection04.json
│ ├── collection05.json
│ ├── collection06.json
│ ├── collection07.json
│ ├── collection08.json
│ ├── collection09.json
│ ├── collection10.json
│ ├── collection11.json
│ ├── collection12.json
│ ├── collection13.json
│ ├── collection14.json
│ ├── collection15.json
│ ├── collection16.json
│ ├── collection17.json
│ ├── collection18.json
│ ├── collection19.json
│ ├── collection20.json
│ ├── collection21.json
│ ├── collection22.json
│ ├── collection23.json
│ ├── collection24.json
│ ├── collection25.json
│ ├── collection26.json
│ ├── collection27.json
│ ├── collection28.json
│ ├── collection29.json
│ ├── collection30.json
│ ├── collection31.json
│ ├── collection32.json
│ ├── collection33.json
│ ├── collection34.json
│ ├── collection35.json
│ ├── collection36.json
│ ├── collection37.json
│ ├── collection38.json
│ ├── collection39.json
│ ├── collection40.json
│ ├── collection41.json
│ ├── collection42.json
│ ├── collection43.json
│ ├── collection44.json
│ ├── collection45.json
│ ├── collection46.json
│ ├── collection47.json
│ ├── collection48.json
│ ├── collection49.json
│ ├── collection50.json
│ ├── collection51.json
│ ├── collection52.json
│ ├── collection53.json
│ ├── collection54.json
│ ├── collection55.json
│ ├── collection56.json
│ └── collection57.json
├── graphQlProxy.js
├── resetCmr.js
├── setupCmr.js
├── startCmr.js
└── stopCmr.js
├── sharedConstants
└── mmtCookie.js
├── sharedUtils
├── __tests__
│ └── getConfig.test.js
└── getConfig.js
├── static.config.json
├── static
└── src
│ ├── assets
│ └── images
│ │ ├── logos
│ │ ├── nasa-meatball-new.svg
│ │ └── nasa-outline-new.svg
│ │ └── yellow-sea-swirls.jpg
│ ├── css
│ ├── eui
│ │ ├── _nested-item-picker.scss
│ │ └── index.scss
│ ├── index.scss
│ ├── initial
│ │ └── index.scss
│ └── vendor
│ │ ├── bootstrap
│ │ ├── _variables.scss
│ │ ├── index.scss
│ │ └── overrides
│ │ │ ├── _navbar.scss
│ │ │ ├── _table.scss
│ │ │ └── index.scss
│ │ └── index.scss
│ ├── js
│ ├── App.jsx
│ ├── __tests__
│ │ └── App.test.jsx
│ ├── components
│ │ ├── AboutModal
│ │ │ ├── AboutModal.jsx
│ │ │ └── __tests__
│ │ │ │ └── AboutModal.test.jsx
│ │ ├── AssociatedCollectionPermissionsTable
│ │ │ ├── AssociatedCollectionPermissionsTable.jsx
│ │ │ └── __tests__
│ │ │ │ └── AssociatedCollectionPermissionsTable.test.jsx
│ │ ├── AuthCallback
│ │ │ ├── AuthCallback.jsx
│ │ │ └── __tests__
│ │ │ │ └── AuthCallback.test.jsx
│ │ ├── AuthRequiredLayout
│ │ │ ├── AuthRequiredLayout.jsx
│ │ │ └── __tests__
│ │ │ │ └── AuthRequiredLayout.test.jsx
│ │ ├── BoundingRectangleField
│ │ │ ├── BoundingRectangleField.jsx
│ │ │ ├── BoundingRectangleField.scss
│ │ │ └── __tests__
│ │ │ │ └── BoundingRectangleField.test.jsx
│ │ ├── Button
│ │ │ ├── Button.jsx
│ │ │ ├── Button.scss
│ │ │ └── __tests__
│ │ │ │ └── Button.test.jsx
│ │ ├── CheckPermissions
│ │ │ ├── CheckPermissions.jsx
│ │ │ └── __tests__
│ │ │ │ └── CheckPermissions.test.jsx
│ │ ├── ChooseProviderModal
│ │ │ ├── ChooseProviderModal.jsx
│ │ │ └── __tests__
│ │ │ │ └── ChooseProviderModal.test.jsx
│ │ ├── CollectionAssociationForm
│ │ │ ├── CollectionAssociationForm.jsx
│ │ │ └── __tests__
│ │ │ │ ├── CollectionAssociationForm.test.jsx
│ │ │ │ └── __mocks__
│ │ │ │ └── CollectionAssociationResults.js
│ │ ├── CollectionSelector
│ │ │ ├── CollectionSelector.jsx
│ │ │ ├── CollectionSelector.scss
│ │ │ └── __tests__
│ │ │ │ └── CollectionSelector.test.jsx
│ │ ├── ControlledPaginatedContent
│ │ │ ├── ControlledPaginatedContent.jsx
│ │ │ └── __tests__
│ │ │ │ └── ControlledPaginatedContent.test.jsx
│ │ ├── CustomArrayFieldTemplate
│ │ │ ├── CustomArrayFieldTemplate.jsx
│ │ │ ├── CustomArrayFieldTemplate.scss
│ │ │ └── __tests__
│ │ │ │ └── CustomArrayFieldTemplate.test.jsx
│ │ ├── CustomAsyncMultiSelectWidget
│ │ │ ├── CustomAsyncMultiSelectWidget.jsx
│ │ │ └── __tests__
│ │ │ │ └── CustomAsyncMultiSelectWidget.test.jsx
│ │ ├── CustomCheckboxWidget
│ │ │ ├── CustomCheckboxWidget.jsx
│ │ │ └── __tests__
│ │ │ │ └── CustomCheckboxWidget.test.jsx
│ │ ├── CustomCountrySelectWidget
│ │ │ ├── CustomCountrySelectWidget.jsx
│ │ │ └── __tests__
│ │ │ │ └── CustomCountrySelectWidget.test.jsx
│ │ ├── CustomDateTimeWidget
│ │ │ ├── CustomDateTimeWidget.jsx
│ │ │ └── __tests__
│ │ │ │ └── CustomDateTimeWidget.test.jsx
│ │ ├── CustomFieldTemplate
│ │ │ ├── CustomFieldTemplate.jsx
│ │ │ └── __tests__
│ │ │ │ └── CustomFieldTemplate.test.jsx
│ │ ├── CustomMenu
│ │ │ └── CustomMenu.jsx
│ │ ├── CustomModal
│ │ │ ├── CustomModal.jsx
│ │ │ └── __tests__
│ │ │ │ └── CustomModal.test.jsx
│ │ ├── CustomMultiSelectWidget
│ │ │ ├── CustomMultiSelectWidget.jsx
│ │ │ └── __tests__
│ │ │ │ └── CustomMultiSelectWidget.test.jsx
│ │ ├── CustomRadioWidget
│ │ │ ├── CustomRadioWidget.jsx
│ │ │ └── __tests__
│ │ │ │ └── CustomRadioWidget.test.jsx
│ │ ├── CustomSelectWidget
│ │ │ ├── CustomSelectWidget.jsx
│ │ │ └── __tests__
│ │ │ │ └── CustomSelectWidget.test.jsx
│ │ ├── CustomTextWidget
│ │ │ ├── CustomTextWidget.jsx
│ │ │ ├── CustomTextWidget.scss
│ │ │ └── __tests__
│ │ │ │ └── CustomTextWidget.test.jsx
│ │ ├── CustomTextareaWidget
│ │ │ ├── CustomTextareaWidget.jsx
│ │ │ ├── CustomTextareaWidget.scss
│ │ │ └── __tests__
│ │ │ │ └── CustomTextareaWidget.test.jsx
│ │ ├── CustomTitleField
│ │ │ ├── CustomTitleField.jsx
│ │ │ ├── CustomTitleField.scss
│ │ │ └── __tests__
│ │ │ │ └── CustomTitleField.test.jsx
│ │ ├── CustomTitleFieldTemplate
│ │ │ ├── CustomTitleFieldTemplate.jsx
│ │ │ ├── CustomTitleFieldTemplate.scss
│ │ │ └── __tests__
│ │ │ │ └── CustomTitleFieldTemplate.test.jsx
│ │ ├── CustomToggle
│ │ │ └── CustomToggle.jsx
│ │ ├── CustomWidgetWrapper
│ │ │ ├── CustomWidgetWrapper.jsx
│ │ │ ├── CustomWidgetWrapper.scss
│ │ │ └── __tests__
│ │ │ │ └── CustomWidgetWrapper.test.jsx
│ │ ├── DeleteConfirmationModal
│ │ │ ├── DeleteConfirmationModal.jsx
│ │ │ └── __tests__
│ │ │ │ └── DeleteConfirmationModal.test.jsx
│ │ ├── DraftList
│ │ │ ├── DraftList.jsx
│ │ │ └── __tests__
│ │ │ │ ├── DraftList.test.jsx
│ │ │ │ └── __mocks__
│ │ │ │ └── DraftListMocks.js
│ │ ├── DraftPreview
│ │ │ ├── DraftPreview.jsx
│ │ │ ├── DraftPreview.scss
│ │ │ └── __tests__
│ │ │ │ └── DraftPreview.test.jsx
│ │ ├── DraftPreviewPlaceholder
│ │ │ ├── DraftPreviewPlaceholder.jsx
│ │ │ └── __tests__
│ │ │ │ └── DraftPreviewPlaceholder.test.jsx
│ │ ├── EllipsisLink
│ │ │ ├── EllipsisLink.jsx
│ │ │ └── __tests__
│ │ │ │ └── EllipsisLink.test.jsx
│ │ ├── EllipsisText
│ │ │ ├── EllipsisText.jsx
│ │ │ └── __tests__
│ │ │ │ └── EllipsisText.test.jsx
│ │ ├── ErrorBanner
│ │ │ ├── ErrorBanner.jsx
│ │ │ └── __tests__
│ │ │ │ └── ErrorBanner.test.jsx
│ │ ├── ErrorBoundary
│ │ │ ├── ErrorBoundary.jsx
│ │ │ └── __tests__
│ │ │ │ └── ErrorBoundary.test.jsx
│ │ ├── ErrorPageNotFound
│ │ │ ├── ErrorPageNotFound.jsx
│ │ │ ├── ErrorPageNotFound.scss
│ │ │ └── __tests__
│ │ │ │ └── ErrorPageNotFound.test.jsx
│ │ ├── Footer
│ │ │ ├── Footer.jsx
│ │ │ ├── Footer.scss
│ │ │ └── __tests__
│ │ │ │ └── Footer.test.jsx
│ │ ├── For
│ │ │ ├── For.jsx
│ │ │ └── __tests__
│ │ │ │ └── For.test.jsx
│ │ ├── FormNavigation
│ │ │ ├── FormNavigation.jsx
│ │ │ ├── FormNavigation.scss
│ │ │ └── __tests__
│ │ │ │ └── FormNavigation.test.jsx
│ │ ├── GenerateKeywordReportModal
│ │ │ ├── GenerateKeywordReportModal.jsx
│ │ │ └── __tests__
│ │ │ │ └── GenerateKeywordReportModal.test.jsx
│ │ ├── GridCheckboxPanel
│ │ │ ├── GridCheckboxPanel.jsx
│ │ │ └── __tests__
│ │ │ │ └── GridCheckboxPanel.test.jsx
│ │ ├── GridCol
│ │ │ ├── GridCol.jsx
│ │ │ └── __tests__
│ │ │ │ └── GridCol.test.jsx
│ │ ├── GridControlledField
│ │ │ ├── GridControlledField.jsx
│ │ │ └── __tests__
│ │ │ │ ├── GridControlledField.test.jsx
│ │ │ │ └── __mocks__
│ │ │ │ ├── providersKeywords.js
│ │ │ │ ├── relatedUrlsKeywords.js
│ │ │ │ ├── selectProps.js
│ │ │ │ └── textProps.js
│ │ ├── GridField
│ │ │ ├── GridField.jsx
│ │ │ └── __tests__
│ │ │ │ └── GridField.test.jsx
│ │ ├── GridGroupedSinglePanel
│ │ │ ├── GridGroupedSinglePanel.jsx
│ │ │ └── __tests__
│ │ │ │ └── GridGroupedSinglePanel.test.jsx
│ │ ├── GridLayout
│ │ │ ├── GridLayout.jsx
│ │ │ ├── GridLayout.scss
│ │ │ └── __tests__
│ │ │ │ └── GridLayout.test.jsx
│ │ ├── GridRow
│ │ │ ├── GridRow.jsx
│ │ │ ├── GridRow.scss
│ │ │ └── __tests__
│ │ │ │ └── GridRow.test.jsx
│ │ ├── Group
│ │ │ ├── Group.jsx
│ │ │ └── __tests__
│ │ │ │ └── Group.test.jsx
│ │ ├── GroupForm
│ │ │ ├── GroupForm.jsx
│ │ │ └── __tests__
│ │ │ │ └── GroupForm.test.jsx
│ │ ├── GroupList
│ │ │ ├── GroupList.jsx
│ │ │ └── __tests__
│ │ │ │ └── GroupList.test.jsx
│ │ ├── GroupPermissionSelect
│ │ │ ├── GroupPermissionSelect.jsx
│ │ │ └── __tests__
│ │ │ │ └── GroupPermissionSelect.test.jsx
│ │ ├── GroupSearchForm
│ │ │ ├── GroupSearchForm.jsx
│ │ │ └── __tests__
│ │ │ │ └── GroupSearchForm.test.jsx
│ │ ├── Header
│ │ │ ├── Header.jsx
│ │ │ ├── Header.scss
│ │ │ └── __tests__
│ │ │ │ ├── Header.test.jsx
│ │ │ │ └── __mocks__
│ │ │ │ └── providerResults.js
│ │ ├── InstrumentField
│ │ │ ├── InstrumentField.jsx
│ │ │ ├── InstrumentField.scss
│ │ │ └── __tests__
│ │ │ │ └── InstrumentField.test.jsx
│ │ ├── JsonFileUploadModal
│ │ │ ├── JsonFileUploadModal.jsx
│ │ │ ├── JsonFileUploadModal.scss
│ │ │ └── __tests__
│ │ │ │ └── JsonFileUploadModal.test.jsx
│ │ ├── JsonPreview
│ │ │ ├── JsonPreview.jsx
│ │ │ └── __tests__
│ │ │ │ └── JsonPreview.test.jsx
│ │ ├── KeywordForm
│ │ │ ├── KeywordForm.jsx
│ │ │ ├── KeywordForm.scss
│ │ │ └── __tests__
│ │ │ │ └── KeywordForm.test.jsx
│ │ ├── KeywordPicker
│ │ │ ├── KeywordPicker.jsx
│ │ │ ├── KeywordPicker.scss
│ │ │ └── __tests__
│ │ │ │ └── KeywordPicker.test.jsx
│ │ ├── KeywordRecommendations
│ │ │ ├── KeywordRecommendations.jsx
│ │ │ └── __tests__
│ │ │ │ └── KeywordRecommendations.test.jsx
│ │ ├── KeywordRecommendationsKeyword
│ │ │ ├── KeywordRecommendationsKeyword.jsx
│ │ │ ├── KeywordRecommendationsKeyword.scss
│ │ │ └── __tests__
│ │ │ │ └── KeywordRecommendationsKeyword.test.jsx
│ │ ├── KeywordTree
│ │ │ ├── KeywordTree.jsx
│ │ │ ├── KeywordTree.scss
│ │ │ └── __tests__
│ │ │ │ └── KeywordTree.test.jsx
│ │ ├── KeywordTreeContextMenu
│ │ │ ├── KeywordTreeContextMenu.jsx
│ │ │ ├── KeywordTreeContextMenu.scss
│ │ │ └── __tests__
│ │ │ │ └── KeywordTreeContextMenu.test.jsx
│ │ ├── KeywordTreeCustomNode
│ │ │ ├── KeywordTreeCustomNode.jsx
│ │ │ ├── KeywordTreeCustomNode.scss
│ │ │ └── __tests__
│ │ │ │ └── KeywordTreeCustomNode.test.jsx
│ │ ├── KeywordTreePlaceHolder
│ │ │ ├── KeywordTreePlaceHolder.jsx
│ │ │ ├── KeywordTreePlaceHolder.scss
│ │ │ └── __tests__
│ │ │ │ └── KeywordTreePlaceHolder.test.jsx
│ │ ├── KmsConceptSchemeSelector
│ │ │ ├── KmsConceptSchemeSelector.jsx
│ │ │ └── __tests__
│ │ │ │ └── KmsConceptSchemeSelector.test.jsx
│ │ ├── KmsConceptSelectionEditModal
│ │ │ ├── KmsConceptSelectionEditModal.jsx
│ │ │ ├── KmsConceptSelectionEditModal.scss
│ │ │ └── __tests__
│ │ │ │ └── KmsConceptSelectionEditModal.test.jsx
│ │ ├── KmsConceptSelectionWidget
│ │ │ ├── KmsConceptSelectionWidget.jsx
│ │ │ ├── KmsConceptSelectionWidget.scss
│ │ │ └── __tests__
│ │ │ │ └── KMSConceptSelectionWidget.test.jsx
│ │ ├── KmsConceptVersionSelector
│ │ │ ├── KmsConceptVersionSelector.jsx
│ │ │ └── __tests__
│ │ │ │ └── KmsConceptVersionSelector.test.jsx
│ │ ├── Layout
│ │ │ ├── Layout.jsx
│ │ │ ├── Layout.scss
│ │ │ └── __tests__
│ │ │ │ └── Layout.test.jsx
│ │ ├── LayoutUnauthenticated
│ │ │ ├── LayoutUnauthenticated.jsx
│ │ │ └── __tests__
│ │ │ │ └── LayoutUnauthenticated.test.jsx
│ │ ├── LoadingBanner
│ │ │ ├── LoadingBanner.jsx
│ │ │ └── __tests__
│ │ │ │ ├── LoadingBanner.test.jsx
│ │ │ │ └── __snapshots__
│ │ │ │ ├── LoadingBanner.test.js.snap
│ │ │ │ └── LoadingBanner.test.jsx.snap
│ │ ├── LoadingTable
│ │ │ ├── LoadingTable.jsx
│ │ │ └── __tests__
│ │ │ │ └── LoadingTable.test.jsx
│ │ ├── ManageCollectionAssociation
│ │ │ ├── ManageCollectionAssociation.jsx
│ │ │ └── __tests__
│ │ │ │ ├── ManageCollectionAssociation.test.jsx
│ │ │ │ └── __mocks__
│ │ │ │ └── manageCollectionAssociationResults.js
│ │ ├── ManageServiceAssociations
│ │ │ ├── ManageServiceAssociations.jsx
│ │ │ └── __tests__
│ │ │ │ ├── ManageServiceAssociations.test.jsx
│ │ │ │ └── __mocks__
│ │ │ │ └── serviceAssociationsResponses.js
│ │ ├── MetadataForm
│ │ │ ├── MetadataForm.jsx
│ │ │ ├── MetadataForm.scss
│ │ │ └── __tests__
│ │ │ │ └── MetadataForm.test.jsx
│ │ ├── MetadataPreview
│ │ │ ├── MetadataPreview.jsx
│ │ │ └── __tests__
│ │ │ │ ├── MetadataPreview.test.jsx
│ │ │ │ └── __mocks__
│ │ │ │ └── MatadataPreviewMocks.js
│ │ ├── MetadataPreviewPlaceholder
│ │ │ ├── MetadataPreviewPlaceholder.jsx
│ │ │ └── __tests__
│ │ │ │ └── MetadataPreviewPlaceholder.test.jsx
│ │ ├── NavigationItem
│ │ │ ├── NavigationItem.jsx
│ │ │ ├── NavigationItem.scss
│ │ │ └── __tests__
│ │ │ │ └── NavigationItem.test.jsx
│ │ ├── NavigationItemError
│ │ │ ├── NavigationItemError.jsx
│ │ │ ├── NavigationItemError.scss
│ │ │ └── __tests__
│ │ │ │ └── NavigationItemError.test.jsx
│ │ ├── Notifications
│ │ │ ├── Notifications.jsx
│ │ │ ├── Notifications.scss
│ │ │ └── __tests__
│ │ │ │ └── Notifications.test.jsx
│ │ ├── ObjectField
│ │ │ ├── ObjectField.js
│ │ │ └── __tests__
│ │ │ │ └── ObjectField.test.jsx
│ │ ├── OneOfField
│ │ │ ├── OneOfField.jsx
│ │ │ └── __tests__
│ │ │ │ └── OneOfField.test.jsx
│ │ ├── OrderOption
│ │ │ ├── OrderOption.jsx
│ │ │ └── __tests__
│ │ │ │ └── OrderOption.test.jsx
│ │ ├── OrderOptionForm
│ │ │ ├── OrderOptionForm.jsx
│ │ │ └── __tests__
│ │ │ │ └── OrderOptionForm.test.jsx
│ │ ├── OrderOptionList
│ │ │ ├── OrderOptionList.jsx
│ │ │ └── __tests__
│ │ │ │ └── OrderOptionList.test.jsx
│ │ ├── Page
│ │ │ ├── Page.jsx
│ │ │ ├── Page.scss
│ │ │ └── __tests__
│ │ │ │ └── Page.test.jsx
│ │ ├── PageHeader
│ │ │ └── PageHeader.jsx
│ │ ├── Pagination
│ │ │ ├── Pagination.jsx
│ │ │ └── __tests__
│ │ │ │ └── Pagination.test.jsx
│ │ ├── Panel
│ │ │ ├── Panel.jsx
│ │ │ ├── Panel.scss
│ │ │ └── __tests__
│ │ │ │ └── Panel.test.jsx
│ │ ├── Permission
│ │ │ ├── Permission.jsx
│ │ │ ├── Permission.scss
│ │ │ └── __tests__
│ │ │ │ └── Permission.test.jsx
│ │ ├── PermissionCollectionTable
│ │ │ ├── PermissionCollectionTable.jsx
│ │ │ └── __tests__
│ │ │ │ ├── PermissionCollectionTable.test.jsx
│ │ │ │ └── __mocks__
│ │ │ │ └── permissionCollectionTableResults.js
│ │ ├── PermissionForm
│ │ │ ├── PermissionForm.jsx
│ │ │ └── __tests__
│ │ │ │ └── PermissionForm.test.jsx
│ │ ├── PermissionList
│ │ │ ├── PermissionList.jsx
│ │ │ └── __tests__
│ │ │ │ └── PermissionList.test.jsx
│ │ ├── PlatformField
│ │ │ ├── PlatformField.jsx
│ │ │ ├── PlatformField.scss
│ │ │ └── __tests__
│ │ │ │ └── PlatformField.test.jsx
│ │ ├── PreviewMapTemplate
│ │ │ ├── PreviewMapTemplate.jsx
│ │ │ ├── PreviewMapTemplate.scss
│ │ │ └── __tests__
│ │ │ │ └── PreviewMapTemplate.test.jsx
│ │ ├── PreviewProgress
│ │ │ ├── PreviewProgress.jsx
│ │ │ ├── PreviewProgress.scss
│ │ │ └── __tests__
│ │ │ │ ├── PreviewProgress.test.jsx
│ │ │ │ └── __mocks__
│ │ │ │ ├── configuration.js
│ │ │ │ └── schema.js
│ │ ├── PrimaryNavigation
│ │ │ ├── PrimaryNavigation.jsx
│ │ │ ├── PrimaryNavigation.scss
│ │ │ └── __tests__
│ │ │ │ └── PrimaryNavigation.test.jsx
│ │ ├── PrimaryNavigationGroup
│ │ │ ├── PrimaryNavigationGroup.jsx
│ │ │ └── __tests__
│ │ │ │ └── PrimaryNavigationGroup.test.jsx
│ │ ├── PrimaryNavigationLink
│ │ │ ├── PrimaryNavigationLink.jsx
│ │ │ ├── PrimaryNavigationLink.scss
│ │ │ └── __tests__
│ │ │ │ └── PrimaryNavigationLink.test.jsx
│ │ ├── ProgressField
│ │ │ ├── ProgressField.jsx
│ │ │ ├── ProgressField.scss
│ │ │ └── __tests__
│ │ │ │ └── ProgressField.test.jsx
│ │ ├── ProgressSection
│ │ │ ├── ProgressSection.jsx
│ │ │ ├── ProgressSection.scss
│ │ │ └── __tests__
│ │ │ │ └── ProgressSection.test.jsx
│ │ ├── ProviderPermissions
│ │ │ ├── ProviderPermissions.jsx
│ │ │ └── __tests__
│ │ │ │ └── ProviderPermissions.test.jsx
│ │ ├── Providers
│ │ │ ├── Providers.jsx
│ │ │ └── __tests__
│ │ │ │ └── Providers.test.jsx
│ │ ├── PublishPreview
│ │ │ ├── PublishPreview.jsx
│ │ │ ├── PublishPreview.scss
│ │ │ └── __tests__
│ │ │ │ ├── PublishPreview.test.jsx
│ │ │ │ └── __mocks__
│ │ │ │ └── publishPreview.js
│ │ ├── RevisionList
│ │ │ ├── RevisionList.jsx
│ │ │ └── __tests__
│ │ │ │ ├── RevisionList.test.jsx
│ │ │ │ └── __mocks__
│ │ │ │ └── revisionResults.js
│ │ ├── StreetAddressField
│ │ │ ├── StreetAddressField.jsx
│ │ │ ├── StreetAddressField.scss
│ │ │ └── __tests__
│ │ │ │ └── StreetAddressField.test.jsx
│ │ ├── SystemPermissions
│ │ │ ├── SystemPermissions.jsx
│ │ │ └── __tests__
│ │ │ │ └── SystemPermissions.test.jsx
│ │ ├── Table
│ │ │ ├── Table.jsx
│ │ │ ├── Table.scss
│ │ │ └── __tests__
│ │ │ │ └── Table.test.jsx
│ │ ├── TemplateForm
│ │ │ ├── TemplateForm.jsx
│ │ │ └── __tests__
│ │ │ │ └── TemplateForm.test.jsx
│ │ ├── TemplateList
│ │ │ ├── TemplateList.jsx
│ │ │ └── __tests__
│ │ │ │ └── TemplateList.test.jsx
│ │ ├── TemplatePreview
│ │ │ ├── TemplatePreview.jsx
│ │ │ ├── TemplatePreview.scss
│ │ │ └── __tests__
│ │ │ │ └── TemplatePreview.test.jsx
│ │ └── UniqueItemsArray
│ │ │ ├── UniqueItemsArray.jsx
│ │ │ ├── UniqueItemsArray.scss
│ │ │ └── __tests__
│ │ │ └── UniqueItemsArray.test.jsx
│ ├── constants
│ │ ├── conceptIdTypes.js
│ │ ├── conceptTypeDraftQueries.js
│ │ ├── conceptTypeDraftsQueries.js
│ │ ├── conceptTypeQueries.js
│ │ ├── conceptTypes.js
│ │ ├── dateFormat.js
│ │ ├── deleteMutationTypes.js
│ │ ├── draftConceptIdTypes.js
│ │ ├── keywordsActions.js
│ │ ├── languageArray.js
│ │ ├── notificationsActions.js
│ │ ├── processingLevel.js
│ │ ├── progressCircleTypes.js
│ │ ├── providerIdentityPermissions.js
│ │ ├── redirectsMap
│ │ │ └── redirectsMap.js
│ │ ├── restoreRevisionMutations.js
│ │ ├── saveTypes.js
│ │ ├── saveTypesToHumanizedStringMap.js
│ │ ├── systemIdentityPermissions.js
│ │ ├── typeParamToHumanizedStringMap.js
│ │ ├── urlTypes.js
│ │ └── urlValueToConceptStringMap.js
│ ├── context
│ │ ├── AppContext.js
│ │ ├── AuthContext.js
│ │ └── NotificationsContext.js
│ ├── hooks
│ │ ├── __tests__
│ │ │ ├── useAccessibleEvent.test.jsx
│ │ │ ├── useAvailableProviders.test.jsx
│ │ │ ├── useControlledKeywords.test.jsx
│ │ │ ├── useDraftsQuery.test.jsx
│ │ │ ├── useIngestDraftMutation.test.jsx
│ │ │ ├── usePermissions.test.jsx
│ │ │ └── usePublishMutation.test.jsx
│ │ ├── useAccessibleEvent.js
│ │ ├── useAppContext.js
│ │ ├── useAuthContext.js
│ │ ├── useAvailableProviders.js
│ │ ├── useControlledKeywords.js
│ │ ├── useDraftsQuery.js
│ │ ├── useIngestDraftMutation.js
│ │ ├── useKeywords.js
│ │ ├── useMMTCookie.js
│ │ ├── useNotificationsContext.js
│ │ ├── usePermissions.js
│ │ └── usePublishMutation.js
│ ├── operations
│ │ ├── mutations
│ │ │ ├── createAcl.js
│ │ │ ├── createAssociation.js
│ │ │ ├── createGroup.js
│ │ │ ├── createOrderOption.js
│ │ │ ├── deleteAcl.js
│ │ │ ├── deleteAssociation.js
│ │ │ ├── deleteCitation.js
│ │ │ ├── deleteCollection.js
│ │ │ ├── deleteDraft.js
│ │ │ ├── deleteGroup.js
│ │ │ ├── deleteOrderOption.js
│ │ │ ├── deleteService.js
│ │ │ ├── deleteTool.js
│ │ │ ├── deleteVariable.js
│ │ │ ├── deleteVisualization.js
│ │ │ ├── ingestDraft.js
│ │ │ ├── publishDraft.js
│ │ │ ├── restoreCitationRevision.js
│ │ │ ├── restoreCollectionRevision.js
│ │ │ ├── restoreServiceRevision.js
│ │ │ ├── restoreToolRevision.js
│ │ │ ├── restoreVariableRevision.js
│ │ │ ├── restoreVisualizationRevision.js
│ │ │ ├── updateAcl.js
│ │ │ ├── updateGroup.js
│ │ │ └── updateOrderOption.js
│ │ ├── queries
│ │ │ ├── getAvailableProviders.js
│ │ │ ├── getCitation.js
│ │ │ ├── getCitationDraft.js
│ │ │ ├── getCitationDrafts.js
│ │ │ ├── getCitations.js
│ │ │ ├── getCollection.js
│ │ │ ├── getCollectionDraft.js
│ │ │ ├── getCollectionDrafts.js
│ │ │ ├── getCollectionForPermissionForm.js
│ │ │ ├── getCollectionPermission.js
│ │ │ ├── getCollectionPermissions.js
│ │ │ ├── getCollectionRevisions.js
│ │ │ ├── getCollections.js
│ │ │ ├── getGroup.js
│ │ │ ├── getGroupAcls.js
│ │ │ ├── getGroups.js
│ │ │ ├── getGroupsForPermissionSelect.js
│ │ │ ├── getOrderOption.js
│ │ │ ├── getOrderOptions.js
│ │ │ ├── getPermissionCollections.js
│ │ │ ├── getPermissions.js
│ │ │ ├── getProviderIdentityPermissions.js
│ │ │ ├── getProviders.js
│ │ │ ├── getService.js
│ │ │ ├── getServiceAssociations.js
│ │ │ ├── getServiceDraft.js
│ │ │ ├── getServiceDrafts.js
│ │ │ ├── getServices.js
│ │ │ ├── getSystemIdentityPermissions.js
│ │ │ ├── getTool.js
│ │ │ ├── getToolDraft.js
│ │ │ ├── getToolDrafts.js
│ │ │ ├── getTools.js
│ │ │ ├── getVariable.js
│ │ │ ├── getVariableDraft.js
│ │ │ ├── getVariableDrafts.js
│ │ │ ├── getVariables.js
│ │ │ ├── getVisualization.js
│ │ │ ├── getVisualizationDraft.js
│ │ │ ├── getVisualizationDrafts.js
│ │ │ └── getVisualizations.js
│ │ └── utils
│ │ │ ├── generateCreateAclMutation.js
│ │ │ ├── generateDeleteAclMutation.js
│ │ │ └── generateUpdateAclMutation.js
│ ├── pages
│ │ ├── CollectionAssociationFormPage
│ │ │ ├── CollectionAssociationFormPage.jsx
│ │ │ └── __tests__
│ │ │ │ └── CollectionAssociationFormPage.test.jsx
│ │ ├── CollectionSelectorPage
│ │ │ └── CollectionSelectorPage.jsx
│ │ ├── DraftListPage
│ │ │ ├── DraftListPage.jsx
│ │ │ └── __tests__
│ │ │ │ └── DraftListPage.test.jsx
│ │ ├── DraftPage
│ │ │ ├── DraftPage.jsx
│ │ │ └── __tests__
│ │ │ │ └── DraftPage.test.jsx
│ │ ├── GroupFormPage
│ │ │ ├── GroupFormPage.jsx
│ │ │ └── __tests__
│ │ │ │ └── GroupFormPage.test.jsx
│ │ ├── GroupListPage
│ │ │ ├── GroupListPage.jsx
│ │ │ └── __tests__
│ │ │ │ └── GroupListPage.test.jsx
│ │ ├── GroupPage
│ │ │ ├── GroupPage.jsx
│ │ │ └── __tests__
│ │ │ │ └── GroupPage.test.jsx
│ │ ├── HomePage
│ │ │ ├── HomePage.jsx
│ │ │ ├── HomePage.scss
│ │ │ └── __tests__
│ │ │ │ └── HomePage.test.jsx
│ │ ├── KeywordManagerPage
│ │ │ ├── KeywordManagerPage.jsx
│ │ │ ├── KeywordManagerPage.scss
│ │ │ └── __tests__
│ │ │ │ └── KeywordManagerPage.test.jsx
│ │ ├── LogoutPage
│ │ │ ├── LogoutPage.jsx
│ │ │ └── __tests__
│ │ │ │ └── LogoutPage.test.jsx
│ │ ├── ManageCollectionAssociationPage
│ │ │ ├── ManageCollectionAssociationPage.jsx
│ │ │ └── __tests__
│ │ │ │ ├── ManageCollectionAssociationPage.test.jsx
│ │ │ │ └── __mocks__
│ │ │ │ └── collectionResult.js
│ │ ├── ManageServiceAssociationsPage
│ │ │ ├── ManageServiceAssociationsPage.jsx
│ │ │ └── __tests__
│ │ │ │ └── ManageServiceAssociationsPage.test.jsx
│ │ ├── MetadataFormPage
│ │ │ ├── MetadataFormPage.jsx
│ │ │ └── __tests__
│ │ │ │ └── MetadataFormPage.test.jsx
│ │ ├── OrderOptionFormPage
│ │ │ ├── OrderOptionFormPage.jsx
│ │ │ └── __tests__
│ │ │ │ └── OrderOptionFormPage.test.jsx
│ │ ├── OrderOptionListPage
│ │ │ ├── OrderOptionListPage.jsx
│ │ │ └── __tests__
│ │ │ │ └── OrderOptionListPage.test.jsx
│ │ ├── OrderOptionPage
│ │ │ ├── OrderOptionPage.jsx
│ │ │ └── __tests__
│ │ │ │ └── OrderOptionPage.test.jsx
│ │ ├── PermissionFormPage
│ │ │ ├── PermissionFormPage.jsx
│ │ │ └── __tests__
│ │ │ │ └── PermissionFormPage.test.jsx
│ │ ├── PermissionListPage
│ │ │ ├── PermissionListPage.jsx
│ │ │ └── __tests__
│ │ │ │ └── PermissionListPage.test.jsx
│ │ ├── PermissionPage
│ │ │ ├── PermissionPage.jsx
│ │ │ └── __tests__
│ │ │ │ └── PermissionPage.test.jsx
│ │ ├── ProviderPermissionsPage
│ │ │ ├── ProviderPermissionsPage.jsx
│ │ │ └── __tests__
│ │ │ │ └── ProviderPermissionsPage.test.jsx
│ │ ├── ProvidersPage
│ │ │ ├── ProvidersPage.jsx
│ │ │ └── __tests__
│ │ │ │ └── ProvidersPage.test.jsx
│ │ ├── RevisionListPage
│ │ │ ├── RevisionListPage.jsx
│ │ │ └── __tests__
│ │ │ │ ├── RevisionListPage.test.jsx
│ │ │ │ └── __mocks__
│ │ │ │ └── searchResults.js
│ │ ├── SearchList
│ │ │ ├── SearchList.jsx
│ │ │ └── __tests__
│ │ │ │ ├── SearchList.test.jsx
│ │ │ │ └── __mocks__
│ │ │ │ └── searchResults.js
│ │ ├── SearchPage
│ │ │ ├── SearchPage.jsx
│ │ │ └── __tests__
│ │ │ │ ├── SearchPage.test.jsx
│ │ │ │ └── __mocks__
│ │ │ │ ├── providerResults.js
│ │ │ │ └── searchResults.js
│ │ └── SystemPermissionsPage
│ │ │ ├── SystemPermissionsPage.jsx
│ │ │ └── __tests__
│ │ │ └── SystemPermissionsPage.test.jsx
│ ├── providers
│ │ ├── AppContextProvider
│ │ │ ├── AppContextProvider.jsx
│ │ │ └── __tests__
│ │ │ │ └── AppContextProvider.test.jsx
│ │ ├── AuthContextProvider
│ │ │ ├── AuthContextProvider.jsx
│ │ │ └── __tests__
│ │ │ │ └── AuthContextProvider.test.jsx
│ │ ├── GraphQLProvider
│ │ │ ├── GraphQLProvider.jsx
│ │ │ └── __tests__
│ │ │ │ └── GraphQLProvider.test.jsx
│ │ ├── NotificationsContextProvider
│ │ │ └── NotificationsContextProvider.jsx
│ │ ├── Providers
│ │ │ ├── Providers.jsx
│ │ │ └── __tests__
│ │ │ │ └── Providers.test.jsx
│ │ └── withProviders
│ │ │ └── withProviders.jsx
│ ├── reducers
│ │ ├── __tests__
│ │ │ ├── keywordsReducer.test.js
│ │ │ └── notificationsReducer.test.js
│ │ ├── keywordsReducer.js
│ │ └── notificationsReducer.js
│ ├── schemas
│ │ ├── collectionAssociation.js
│ │ ├── collectionPermission.js
│ │ ├── group.js
│ │ ├── groupSearch.js
│ │ ├── kms
│ │ │ └── urlTypeTool.js
│ │ ├── orderOption.js
│ │ ├── otherSchemasCitSchema.js
│ │ ├── otherSchemasVisSchema.js
│ │ ├── systemGroup.js
│ │ ├── uiForms
│ │ │ ├── citationConfiguration.js
│ │ │ ├── collectionTemplatesConfiguration.js
│ │ │ ├── collectionsConfiguration.js
│ │ │ ├── index.js
│ │ │ ├── serviceConfiguration.js
│ │ │ ├── toolsConfiguration.js
│ │ │ ├── variableConfiguration.js
│ │ │ └── visualizationConfiguration.js
│ │ ├── uiSchemas
│ │ │ ├── CollectionAssociation.js
│ │ │ ├── Group.js
│ │ │ ├── GroupSearch.js
│ │ │ ├── OrderOption.js
│ │ │ ├── SystemGroup.js
│ │ │ ├── SystemGroupSearch.js
│ │ │ ├── citations
│ │ │ │ ├── citationInformation.js
│ │ │ │ ├── citationMetadata.js
│ │ │ │ ├── citationScienceKeywordsUiSchema.js
│ │ │ │ ├── index.js
│ │ │ │ └── relatedIdentifiers.js
│ │ │ ├── collectionPermission.js
│ │ │ ├── collections
│ │ │ │ ├── acquisitionInformation.jsx
│ │ │ │ ├── archiveDistributionInformation.js
│ │ │ │ ├── collectionCitation.js
│ │ │ │ ├── collectionInformation.js
│ │ │ │ ├── dataCenters.js
│ │ │ │ ├── dataContacts.js
│ │ │ │ ├── dataIdentification.js
│ │ │ │ ├── descriptiveKeywords.js
│ │ │ │ ├── index.js
│ │ │ │ ├── metadataInformation.js
│ │ │ │ ├── relatedUrls.js
│ │ │ │ ├── spatialInformation.jsx
│ │ │ │ └── temporalInformation.js
│ │ │ ├── keywords
│ │ │ │ └── editKeyword.js
│ │ │ ├── services
│ │ │ │ ├── descriptiveKeywords.js
│ │ │ │ ├── index.js
│ │ │ │ ├── operationMetadata.js
│ │ │ │ ├── related_urls.js
│ │ │ │ ├── serviceConstraints.js
│ │ │ │ ├── serviceContacts.js
│ │ │ │ ├── serviceInformation.js
│ │ │ │ ├── serviceOptions.js
│ │ │ │ ├── serviceOrganizations.js
│ │ │ │ └── serviceQuality.js
│ │ │ ├── tools
│ │ │ │ ├── compatibilityAndUsability.js
│ │ │ │ ├── descriptiveKeywords.js
│ │ │ │ ├── index.js
│ │ │ │ ├── organization.js
│ │ │ │ ├── potentialAction.js
│ │ │ │ ├── relatedUrls.js
│ │ │ │ ├── toolContacts.js
│ │ │ │ └── toolInformation.js
│ │ │ ├── variables
│ │ │ │ ├── dimensions.js
│ │ │ │ ├── fillValue.js
│ │ │ │ ├── index.js
│ │ │ │ ├── instanceInformation.js
│ │ │ │ ├── measurementIdentifiers.js
│ │ │ │ ├── relatedUrls.js
│ │ │ │ ├── samplingIdentifiers.js
│ │ │ │ ├── scienceKeywords.js
│ │ │ │ ├── sets.js
│ │ │ │ └── variableInformation.js
│ │ │ └── visualizations
│ │ │ │ ├── generation.js
│ │ │ │ ├── index.js
│ │ │ │ ├── scienceKeywords.js
│ │ │ │ ├── spatialExtent.jsx
│ │ │ │ ├── specification.js
│ │ │ │ ├── temporalExtents.js
│ │ │ │ └── visualizationInformation.js
│ │ └── umm
│ │ │ ├── keywordSchema.js
│ │ │ ├── ummCSchema.js
│ │ │ ├── ummCTemplateSchema.js
│ │ │ ├── ummSSchema.js
│ │ │ ├── ummTSchema.js
│ │ │ └── ummVarSchema.js
│ └── utils
│ │ ├── __mocks__
│ │ └── relatedUrls.js
│ │ ├── __tests__
│ │ ├── buildValidationErrors.test.js
│ │ ├── checkForCMRFetchDraftLag.test.js
│ │ ├── clearFormData.test.js
│ │ ├── collectionAssociationSearch.test.js
│ │ ├── constructDownloadableFile.test.js
│ │ ├── convertFormDataToRdf.test.js
│ │ ├── convertToDottedNotation.test.js
│ │ ├── createFormDataFromRdf.test.js
│ │ ├── createPath.test.js
│ │ ├── createResponseFromKeywords.test.js
│ │ ├── createTemplate.test.js
│ │ ├── createUpdateKmsConcept.test.js
│ │ ├── deleteKmsConcept.test.js
│ │ ├── deleteTemplate.test.js
│ │ ├── errorLogger.test.js
│ │ ├── extractErrorsFromGraphQlResponse.test.js
│ │ ├── fetchCmrKeywords.test.js
│ │ ├── getConceptTypeByConcept.test.js
│ │ ├── getConceptTypeByDraftConceptId.test.js
│ │ ├── getFormSchema.test.js
│ │ ├── getHumanizedNameFromTypeParam.test.js
│ │ ├── getKeywordRecommendations.test.js
│ │ ├── getKeywords.test.js
│ │ ├── getKmsConceptFullPaths.test.js
│ │ ├── getKmsConceptSchemes.test.js
│ │ ├── getKmsConceptVersions.test.js
│ │ ├── getKmsKeywordTree.test.js
│ │ ├── getNextFormName.test.js
│ │ ├── getParentFormData.test.js
│ │ ├── getTagCount.test.js
│ │ ├── getTemplate.test.js
│ │ ├── getTemplates.test.js
│ │ ├── getUiSchema.test.js
│ │ ├── getUmmSchema.test.js
│ │ ├── getUmmVersion.test.js
│ │ ├── getVersionName.test.js
│ │ ├── handleShortNameChange.test.js
│ │ ├── handleSort.test.js
│ │ ├── isTokenExpired.test.js
│ │ ├── kmsGetConceptUpdatesReport.test.js
│ │ ├── parseCmrInstrumentsResponse.test.js
│ │ ├── parseCmrResponse.test.js
│ │ ├── parseError.test.js
│ │ ├── prefixProperty.test.js
│ │ ├── publishKmsconceptVersion.test.js
│ │ ├── refreshToken.test.js
│ │ ├── removeEmpty.test.js
│ │ ├── removeMetadataKeys.test.js
│ │ ├── sendKeywordRecommendationsFeedback.test.js
│ │ ├── shouldFocusField.test.js
│ │ ├── shouldHideGetData.test.js
│ │ ├── shouldHideGetService.test.js
│ │ ├── toKebabCase.test.js
│ │ ├── toTitleCase.test.js
│ │ ├── traverseArray.test.js
│ │ ├── updateTemplate.test.js
│ │ ├── validGroupItems.test.js
│ │ ├── validateJson.test.js
│ │ └── walkMap.test.js
│ │ ├── buildValidationErrors.js
│ │ ├── checkForCMRFetchDraftLag.js
│ │ ├── clearFormData.js
│ │ ├── collectionAssociationSearch.js
│ │ ├── constructDownloadableFile.js
│ │ ├── convertFormDataToRdf.js
│ │ ├── convertToDottedNotation.js
│ │ ├── createFormDataFromRdf.js
│ │ ├── createPath.js
│ │ ├── createResponseFromKeywords.js
│ │ ├── createTemplate.js
│ │ ├── createUpdateKmsConcept.js
│ │ ├── deleteKmsConcept.js
│ │ ├── deleteTemplate.js
│ │ ├── errorLogger.js
│ │ ├── extractErrorsFromGraphQlResponse.js
│ │ ├── fetchCmrKeywords.js
│ │ ├── getConceptTypeByConceptId.js
│ │ ├── getConceptTypeByDraftConceptId.js
│ │ ├── getFormSchema.js
│ │ ├── getHumanizedNameFromTypeParam.js
│ │ ├── getKeywordRecommendations.js
│ │ ├── getKeywords.js
│ │ ├── getKmsConceptFullPaths.js
│ │ ├── getKmsConceptSchemes.js
│ │ ├── getKmsConceptVersions.js
│ │ ├── getKmsKeywordTree.js
│ │ ├── getNextFormName.js
│ │ ├── getParentFormData.js
│ │ ├── getTagCount.js
│ │ ├── getTemplate.js
│ │ ├── getTemplates.js
│ │ ├── getUiSchema.js
│ │ ├── getUmmSchema.js
│ │ ├── getUmmVersion.js
│ │ ├── getVersionName.js
│ │ ├── handleShortNameChange.js
│ │ ├── handleSort.js
│ │ ├── isTokenExpired.js
│ │ ├── kmsGetConceptUpdatesReport.js
│ │ ├── parseCmrInstrumentsResponse.js
│ │ ├── parseCmrResponse.js
│ │ ├── parseError.js
│ │ ├── prefixProperty.js
│ │ ├── publishKmsConceptVersion.js
│ │ ├── refreshToken.js
│ │ ├── removeEmpty.js
│ │ ├── removeMetadataKeys.js
│ │ ├── sendKeywordRecommendationsFeedback.js
│ │ ├── shouldFocusField.js
│ │ ├── shouldHideGetData.js
│ │ ├── shouldHideGetService.js
│ │ ├── toKebabCase.js
│ │ ├── toTitleCase.js
│ │ ├── traverseArray.js
│ │ ├── updateTemplate.js
│ │ ├── validGroupItems.js
│ │ ├── validateJson.js
│ │ └── walkMap.js
│ └── main.jsx
├── test-setup.js
└── vite.config.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist
2 | .eslintrc
3 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | // Let eslint know were working as a browser to allow the standard globals (document, window, etc.)
3 | "env": {
4 | "browser": true,
5 | "es2021": true,
6 | "node": true,
7 | "jest": true
8 | },
9 |
10 | "overrides": [
11 | {
12 | "files": [ "**/__tests__/**/*.js?(x)" ],
13 | "extends": ["plugin:testing-library/react"]
14 | }
15 | ],
16 |
17 | "extends": [
18 | "@edsc"
19 | ],
20 |
21 | // Define version settings
22 | "settings": {
23 | "react": {
24 | "pragma": "React",
25 | "version": "detect"
26 | },
27 | "import/resolver": {
28 | "alias": {
29 | "map": [
30 | [ "@", "./static/src" ],
31 | [ "sharedConstants", "./sharedConstants"],
32 | [ "sharedUtils", "./sharedUtils"]
33 | ],
34 | "extensions": [
35 | ".js",
36 | ".jsx",
37 | ".json"
38 | ]
39 | }
40 | }
41 | },
42 |
43 | "parserOptions": {
44 | "requireConfigFile": false,
45 | "babelOptions": {
46 | "presets": [
47 | "@babel/preset-react"
48 | ]
49 | }
50 | },
51 |
52 | // Define any global variables to avoid no-undef errors
53 | "globals": {
54 | "vi": "readonly"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | Before contributing to this project, please review [Contribution.md](https://github.com/nasa/mmt/blob/master/CONTRIBUTING.md).
11 |
12 | ### Description
13 | A clear and concise description of what the bug is.
14 |
15 | **Reproduction Steps**
16 | Steps to reproduce the behavior:
17 | 1. Go to '...'
18 | 2. Click on '....'
19 | 3. Scroll down to '....'
20 | 4. See error
21 |
22 | **Expected behavior**
23 | A clear and concise description of what you expected to happen.
24 |
25 | **Screenshots**
26 | If applicable, add screenshots to help explain your problem.
27 |
28 | **System Information (please complete the following information):**
29 | - OS: [e.g. iOS]
30 | - Browser [e.g. chrome, safari]
31 | - Version [e.g. 22]
32 |
33 | ### Additional context
34 | Add any other context about the problem here.
35 |
36 | ### Acceptance Criteria
37 | Steps to produce expected behavior.
38 |
--------------------------------------------------------------------------------
/.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/mmt/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | # Overview
2 |
3 | ### What is the feature?
4 |
5 | Please summarize the feature or fix.
6 |
7 | ### What is the Solution?
8 |
9 | Summarize what you changed.
10 |
11 | ### What areas of the application does this impact?
12 |
13 | List impacted areas.
14 |
15 | # Testing
16 |
17 | ### Reproduction steps
18 |
19 | - **Environment for testing:**
20 | - **Collection to test with:**
21 |
22 | 1. Step 1
23 | 2. Step 2...
24 |
25 | ### Attachments
26 |
27 | Please include relevant screenshots or files that would be helpful in reviewing and verifying this change.
28 |
29 | # Checklist
30 |
31 | - [ ] I have added automated tests that prove my fix is effective or that my feature works
32 | - [ ] New and existing unit tests pass locally with my changes
33 | - [ ] I have performed a self-review of my own code
34 | - [ ] I have commented my code, particularly in hard-to-understand areas
35 | - [ ] I have made corresponding changes to the documentation
36 | - [ ] My changes generate no new warnings
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Project build files
2 | .dockerignore
3 | .DS_Store
4 | .serverless
5 | .esbuild
6 | coverage
7 | dist
8 | doc
9 | Dockerfile
10 | node_modules
11 | secret.config.json
12 | tmp
13 | .env
14 |
15 | # VSCode Settings
16 | .vscode
17 |
18 | # CI Outputs
19 | junit.xml
20 |
21 | cmr
22 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 20.18.1
2 |
--------------------------------------------------------------------------------
/.stylelintignore:
--------------------------------------------------------------------------------
1 | docs/**/*.css
2 |
--------------------------------------------------------------------------------
/.stylelintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "customSyntax": "postcss-scss",
3 | "extends": [
4 | "stylelint-config-standard",
5 | "stylelint-config-idiomatic-order"
6 | ],
7 | "plugins": [
8 | "stylelint-scss"
9 | ],
10 | "rules": {
11 | "at-rule-no-unknown": null,
12 | "import-notation": "string",
13 | "keyframes-name-pattern": "[a-zA-Z]",
14 | "no-invalid-position-at-import-rule": null,
15 | "property-no-unknown": [
16 | true,
17 | {
18 | "ignoreProperties": ["composes", "compose-with"]
19 | }
20 | ],
21 | "scss/at-rule-no-unknown": true,
22 | "selector-class-pattern": "[a-zA-Z]",
23 | "selector-pseudo-class-no-unknown": [
24 | true,
25 | {
26 | "ignorePseudoClasses": ["global"]
27 | }
28 | ],
29 | "value-keyword-case": [
30 | "lower",
31 | {
32 | "ignoreKeywords": ["/[a-zA-Z]/"]
33 | }
34 | ]
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to MMT
2 |
3 | Thanks for contributing!
4 |
5 | ## Making Changes
6 |
7 | To allow us to incorporate your changes, please use the following process:
8 |
9 | 1. Fork this repository to your personal account.
10 | 2. Create a branch and make your changes.
11 | 3. Test the changes locally/in your personal fork.
12 | 4. Submit a pull request to open a discussion about your proposed changes.
13 | 5. The maintainers will talk with you about it and decide to merge or request additional changes.
14 |
15 | ## Commits
16 |
17 | Our ticketing and CI/CD tools are configured to sync statuses amongst each other. Commits play an important role in this process. Please start all commits with the MMT ticket number associated with your feature, task, or bug. All commit messages should follow the format "MMT-XXXX: [Your commit message here]"
18 |
19 | ## Disclaimer
20 |
21 | The MMT development team will review all pull requests submitted. Only requests that meet the standard of quality set forth by existing code, following the patterns set forth by existing code, and adhering to the design patterns set forth by existing UI elements will be considered and/or accepted.
22 |
23 | For general tips on open source contributions, see [Contributing to Open Source on GitHub](https://guides.github.com/activities/contributing-to-open-source/).
24 |
--------------------------------------------------------------------------------
/babel.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-env",
4 | "@babel/preset-react"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/bin/start-env.js:
--------------------------------------------------------------------------------
1 | const concurrently = require('concurrently')
2 |
3 | // Get the environment from command line arguments
4 | const environment = process.argv[2]
5 |
6 | let password
7 |
8 | switch (environment) {
9 | case 'SIT':
10 | password = process.env.MMT_SIT_PASSWORD
11 | break
12 | case 'UAT':
13 | password = process.env.MMT_UAT_PASSWORD
14 | break
15 | case 'PROD':
16 | password = process.env.MMT_PROD_PASSWORD
17 | break
18 | default:
19 | console.error('Please provide a valid environment (SIT, UAT, or PROD)')
20 | process.exit(1)
21 | }
22 |
23 | if (!password) {
24 | console.error(`No password found for ${environment} environment. Please set MMT_${environment}_PASSWORD environment variable.`)
25 | process.exit(1)
26 | }
27 |
28 | console.log(`Starting ${environment} environment...`)
29 |
30 | concurrently([
31 | {
32 | command: `EDL_PASSWORD='${password}' npm run offline`,
33 | name: 'api'
34 | },
35 | {
36 | command: 'npm run start:proxy',
37 | name: 'proxy'
38 | },
39 | {
40 | command: 'npm run start:app',
41 | name: 'app'
42 | }
43 | ], {
44 | prefix: 'name',
45 | padPrefix: true,
46 | prefixColors: 'auto',
47 | handleInput: true
48 | })
49 |
--------------------------------------------------------------------------------
/bin/start.js:
--------------------------------------------------------------------------------
1 | const concurrently = require('concurrently')
2 |
3 | concurrently([{
4 | command: 'npm run cmr:start_and_setup',
5 | name: 'cmr'
6 | }, {
7 | command: 'npm run start:app',
8 | name: 'vite'
9 | },
10 | {
11 | command: 'npm run offline',
12 | name: 'api'
13 | },
14 | {
15 | command: 'npm run start:proxy',
16 | name: 'proxy'
17 | }], {
18 | prefix: 'name',
19 | padPrefix: true,
20 | prefixColors: 'auto',
21 | handleInput: true
22 | })
23 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "@/*": ["static/src/*"],
6 | "sharedConstants/*": ["sharedConstants/*"],
7 | "sharedUtils/*": ["sharedUtils/*"]
8 | }
9 | },
10 | "include": ["static/src/**/*", "sharedConstants/*", "sharedUtils/*"]
11 | }
12 |
--------------------------------------------------------------------------------
/serverless-configs/aws-cors-configuration.yml:
--------------------------------------------------------------------------------
1 | origin: ${env:MMT_HOST, 'http://localhost:5173'}
2 | headers:
3 | - Access-Control-Allow-Origin
4 | - Access-Control-Allow-Credentials
5 | - Access-Control-Request-Headers
6 | - Access-Control-Request-Methods
7 | - Authorization
8 | - Origin
9 | - User-Agent
10 | allowCredentials: true
11 |
--------------------------------------------------------------------------------
/serverless-infrastructure.yml:
--------------------------------------------------------------------------------
1 | service: mmt-infrastructure
2 |
3 | provider:
4 | name: aws
5 | runtime: nodejs20.x
6 | stage: ${opt:stage, 'dev'}
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 | - ${env:SUBNET_ID_C}
15 |
16 | deploymentBucket:
17 | skipPolicySetup: true
18 |
19 | role: MMTServerlessAppRole
20 |
21 | #
22 | # AWS Infrastructure Resources
23 | #
24 | resources: ${file(./serverless-configs/${self:provider.name}-infrastructure-resources.yml)}
25 |
--------------------------------------------------------------------------------
/serverless/src/createTemplate/__tests__/handler.test.js:
--------------------------------------------------------------------------------
1 | import { mockClient } from 'aws-sdk-client-mock'
2 | import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3'
3 |
4 | import createTemplate from '../handler'
5 |
6 | const s3ClientMock = mockClient(S3Client)
7 |
8 | beforeEach(() => {
9 | vi.clearAllMocks()
10 | s3ClientMock.reset()
11 | })
12 |
13 | describe('createTemplate', () => {
14 | test('saves the template to s3', async () => {
15 | s3ClientMock.on(PutObjectCommand).resolves({
16 | $metadata: {
17 | httpStatusCode: 200,
18 | requestId: undefined,
19 | extendedRequestId: undefined,
20 | cfId: undefined,
21 | attempts: 1,
22 | totalRetryDelay: 0
23 | },
24 | ETag: '"1a7e08244b933e4fea1f920da4988500"'
25 | })
26 |
27 | const event = {
28 | body: JSON.stringify({
29 | TemplateName: 'Test Template',
30 | mock: 'Template Body'
31 | }),
32 | pathParameters: {
33 | providerId: 'MMT-1'
34 | }
35 | }
36 |
37 | const response = await createTemplate(event)
38 |
39 | expect(response.statusCode).toBe(200)
40 | })
41 | })
42 |
--------------------------------------------------------------------------------
/serverless/src/edlAuthorizer/handler.js:
--------------------------------------------------------------------------------
1 | import jwt from 'jsonwebtoken'
2 | import { generatePolicy } from '../utils/authorizer/generatePolicy'
3 | import { downcaseKeys } from '../utils/downcaseKeys'
4 | import fetchEdlProfile from '../utils/fetchEdlProfile'
5 |
6 | /**
7 | * Custom authorizer for API Gateway authentication
8 | * @param {Object} event Details about the HTTP request that it received
9 | * @param {Object} context Methods and properties that provide information about the invocation, function, and execution environment
10 | */
11 | const edlAuthorizer = async (event) => {
12 | const { env } = process
13 | const { JWT_SECRET } = env
14 |
15 | const {
16 | headers = {},
17 | methodArn
18 | } = event
19 |
20 | const { authorization: authorizationToken = '' } = downcaseKeys(headers)
21 |
22 | const [, token] = authorizationToken.split('Bearer ')
23 | const decodedJwt = jwt.verify(token, JWT_SECRET)
24 | const { launchpadToken } = decodedJwt
25 |
26 | const profile = await fetchEdlProfile(launchpadToken)
27 | const { uid } = profile
28 |
29 | if (uid) {
30 | return generatePolicy(uid, 'Allow', methodArn)
31 | }
32 |
33 | throw new Error('Unauthorized')
34 | }
35 |
36 | export default edlAuthorizer
37 |
--------------------------------------------------------------------------------
/serverless/src/errorLogger/__tests__/handler.test.js:
--------------------------------------------------------------------------------
1 | import errorLogger from '../handler'
2 |
3 | beforeEach(() => {
4 | vi.clearAllMocks()
5 | })
6 |
7 | describe('errorLogger', () => {
8 | test('returns a 200 status code', async () => {
9 | const consoleMock = vi.spyOn(console, 'error').mockImplementation(() => vi.fn())
10 |
11 | const event = {
12 | body: JSON.stringify({
13 | message: 'Mock error message',
14 | stack: 'Mock stack trace',
15 | location: 'Mock URL',
16 | action: 'Mock action'
17 | })
18 | }
19 |
20 | const response = await errorLogger(event)
21 |
22 | expect(response.statusCode).toBe(200)
23 | expect(consoleMock).toBeCalledTimes(1)
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/serverless/src/errorLogger/handler.js:
--------------------------------------------------------------------------------
1 | import { getApplicationConfig } from '../../../sharedUtils/getConfig'
2 |
3 | /**
4 | * Logs an error reported by a client
5 | * @param {Object} event Details about the HTTP request that it received
6 | */
7 | const errorLogger = async (event) => {
8 | const { defaultResponseHeaders } = getApplicationConfig()
9 | const { body } = event
10 | const {
11 | message,
12 | stack,
13 | location,
14 | action
15 | } = JSON.parse(body)
16 |
17 | console.error('Error reported', `Action: ${action} - Message: ${message} - Location: ${location} - Stack: ${JSON.stringify(stack)}`)
18 |
19 | return {
20 | statusCode: 200,
21 | headers: defaultResponseHeaders
22 | }
23 | }
24 |
25 | export default errorLogger
26 |
--------------------------------------------------------------------------------
/serverless/src/samlLogin/handler.js:
--------------------------------------------------------------------------------
1 | import { SAML } from '@node-saml/node-saml'
2 |
3 | import { getSamlConfig } from '../../../sharedUtils/getConfig'
4 |
5 | /**
6 | * Handles login authentication
7 | * @param {Object} event Details about the HTTP request that it received
8 | */
9 | const samlLogin = async (event) => {
10 | const options = getSamlConfig()
11 |
12 | // SAML package will not produce an authorization url (see below) without the 'cert' and the 'idpCert' property,
13 | // although it is only necessary if we are validating tokens (which we are not doing at the
14 | // moment)
15 | options.cert = 'fake_cert'
16 | options.idpCert = 'fake_idp_cert'
17 | const saml = new SAML(options)
18 | const { queryStringParameters } = event
19 | const { target: relayState } = queryStringParameters || {}
20 |
21 | const authorizeUrl = await saml.getAuthorizeUrlAsync(relayState, options)
22 |
23 | return {
24 | statusCode: 307,
25 | headers: {
26 | Location: authorizeUrl
27 | }
28 | }
29 | }
30 |
31 | export default samlLogin
32 |
--------------------------------------------------------------------------------
/serverless/src/utils/__tests__/createCookie.test.js:
--------------------------------------------------------------------------------
1 | import createCookie from '../createCookie'
2 |
3 | describe('createCookie', () => {
4 | const OLD_ENV = process.env
5 |
6 | beforeEach(() => {
7 | process.env = {
8 | ...OLD_ENV,
9 | COOKIE_DOMAIN: '.example.com',
10 | JWT_VALID_TIME: '900'
11 | }
12 | })
13 |
14 | afterEach(() => {
15 | process.env = OLD_ENV
16 | })
17 |
18 | describe('when not running locally', () => {
19 | test('returns the cookie string', () => {
20 | expect(createCookie('mock-jwt')).toEqual('_mmt_jwt_development=mock-jwt; SameSite=Strict; Path=/; Domain=.example.com; Max-Age=900; Secure;')
21 | })
22 | })
23 |
24 | describe('when running locally', () => {
25 | test('returns the cookie string', () => {
26 | process.env.IS_OFFLINE = true
27 |
28 | expect(createCookie('mock-jwt')).toEqual('_mmt_jwt_development=mock-jwt; SameSite=Strict; Path=/; Domain=.example.com; Max-Age=900;')
29 | })
30 | })
31 | })
32 |
--------------------------------------------------------------------------------
/serverless/src/utils/__tests__/createJwt.test.js:
--------------------------------------------------------------------------------
1 | import createJwt from '../createJwt'
2 |
3 | beforeEach(() => {
4 | process.env.JWT_SECRET = 'JWT_SECRET'
5 | process.env.JWT_VALID_TIME = '900'
6 |
7 | const date = new Date(2024)
8 | vi.setSystemTime(date)
9 | })
10 |
11 | describe('createJwt', () => {
12 | test('returns a JWT', () => {
13 | const token = createJwt('mock-token', {
14 | uid: 'mock-uid'
15 | })
16 |
17 | expect(token).toEqual('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsYXVuY2hwYWRUb2tlbiI6Im1vY2stdG9rZW4iLCJlZGxQcm9maWxlIjp7InVpZCI6Im1vY2stdWlkIn0sImV4cCI6OTAyLCJpYXQiOjJ9.uPWHrM84rrYM9gvT0XQzdKnE6AzkQfAdlDWRNU2COp4')
18 | })
19 | })
20 |
--------------------------------------------------------------------------------
/serverless/src/utils/__tests__/downcaseKeys.test.js:
--------------------------------------------------------------------------------
1 | import { downcaseKeys } from '../downcaseKeys'
2 |
3 | describe('downcaseKeys', () => {
4 | test('returns an empty object when undefined is provided', () => {
5 | const returnObject = downcaseKeys(undefined)
6 |
7 | expect(returnObject).toMatchObject({})
8 | })
9 |
10 | test('downcases keys', () => {
11 | const returnObject = downcaseKeys({
12 | KEY: 'value'
13 | })
14 |
15 | expect(returnObject).toMatchObject({
16 | key: 'value'
17 | })
18 | })
19 |
20 | test('downcases camel cased keys', () => {
21 | const returnObject = downcaseKeys({
22 | hyphenatedKey: 'value'
23 | })
24 |
25 | expect(returnObject).toMatchObject({
26 | hyphenatedkey: 'value'
27 | })
28 | })
29 |
30 | test('downcases hyphenated keys', () => {
31 | const returnObject = downcaseKeys({
32 | 'Hyphenated-Key': 'value'
33 | })
34 |
35 | expect(returnObject).toMatchObject({
36 | 'hyphenated-key': 'value'
37 | })
38 | })
39 | })
40 |
--------------------------------------------------------------------------------
/serverless/src/utils/__tests__/getS3Client.test.js:
--------------------------------------------------------------------------------
1 | import { getS3Client } from '../getS3Client'
2 |
3 | describe('getS3Client', () => {
4 | test('returns a new S3Client', async () => {
5 | const result = getS3Client()
6 |
7 | expect(result.config).toEqual(expect.objectContaining({
8 | forcePathStyle: true,
9 | endpoint: undefined
10 | }))
11 | })
12 |
13 | describe('when in offline mode', () => {
14 | test('returns a new S3Client', async () => {
15 | process.env.IS_OFFLINE = true
16 | const result = getS3Client()
17 |
18 | expect(result.config).toEqual(expect.objectContaining({
19 | forcePathStyle: true
20 | }))
21 |
22 | const credentials = await result.config.credentials()
23 | expect(credentials).toEqual({
24 | accessKeyId: 'S3RVER',
25 | secretAccessKey: 'S3RVER'
26 | })
27 |
28 | const endpoint = await result.config.endpoint()
29 | expect(endpoint).toEqual({
30 | hostname: 'localhost',
31 | port: 4569,
32 | protocol: 'http:',
33 | path: '/',
34 | query: undefined
35 | })
36 | })
37 | })
38 | })
39 |
--------------------------------------------------------------------------------
/serverless/src/utils/authorizer/generatePolicy.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Generate AuthPolicy for the Authorizer
3 | * @param {String} username username of authenticated uset
4 | * @param {String} effect
5 | * @param {Object} resource
6 | */
7 | export const generatePolicy = (username, effect, resource) => {
8 | const authResponse = {
9 | principalId: username
10 | }
11 |
12 | if (effect && resource) {
13 | const policyDocument = {}
14 | policyDocument.Version = '2012-10-17'
15 | policyDocument.Statement = []
16 | const statementOne = {}
17 | statementOne.Action = 'execute-api:Invoke'
18 | statementOne.Effect = effect
19 | statementOne.Resource = resource
20 | policyDocument.Statement[0] = statementOne
21 |
22 | authResponse.policyDocument = policyDocument
23 | }
24 |
25 | return authResponse
26 | }
27 |
--------------------------------------------------------------------------------
/serverless/src/utils/createCookie.js:
--------------------------------------------------------------------------------
1 | import MMT_COOKIE from 'sharedConstants/mmtCookie'
2 |
3 | /**
4 | * Returns the cookie string with the provided JWT
5 | * @param {String} jwt JWT to use for the cookie value
6 | */
7 | const createCookie = (jwt) => {
8 | const {
9 | COOKIE_DOMAIN,
10 | IS_OFFLINE,
11 | JWT_VALID_TIME
12 | } = process.env
13 |
14 | let cookie = `${MMT_COOKIE}=${jwt}; SameSite=Strict; Path=/; Domain=${COOKIE_DOMAIN}; Max-Age=${JWT_VALID_TIME};`
15 | if (!IS_OFFLINE) {
16 | cookie += ' Secure;'
17 | }
18 |
19 | return cookie
20 | }
21 |
22 | export default createCookie
23 |
--------------------------------------------------------------------------------
/serverless/src/utils/createJwt.js:
--------------------------------------------------------------------------------
1 | import jwt from 'jsonwebtoken'
2 |
3 | /**
4 | * Creates the JWT to return to the client
5 | * @param {String} launchpadToken User's Launchpad token
6 | * @param {Object} edlProfile User's EDL Profile
7 | */
8 | const createJwt = (launchpadToken, edlProfile) => {
9 | const { env } = process
10 | const {
11 | JWT_SECRET,
12 | JWT_VALID_TIME
13 | } = env
14 |
15 | const token = jwt.sign(
16 | {
17 | launchpadToken,
18 | edlProfile,
19 | exp: Math.floor(new Date().getTime() / 1000) + parseInt(JWT_VALID_TIME, 10)
20 | },
21 | JWT_SECRET
22 | )
23 |
24 | return token
25 | }
26 |
27 | export default createJwt
28 |
--------------------------------------------------------------------------------
/serverless/src/utils/downcaseKeys.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Downcase all keys of an object
3 | * @param {Object} obj Object to transform
4 | * @returns Object with all keys downcased
5 | */
6 | export const downcaseKeys = (obj = {}) => {
7 | const entries = Object.entries(obj)
8 |
9 | return Object.fromEntries(
10 | entries.map(([key, value]) => [key.toLowerCase(), value])
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/serverless/src/utils/fetchEdlClientToken.js:
--------------------------------------------------------------------------------
1 | import { getEdlConfig } from '../../../sharedUtils/getConfig'
2 |
3 | /**
4 | * The EDL client token is used for retrieving/modifying user/groups in URS.
5 | * @returns the EDL client token
6 | */
7 | const fetchEdlClientToken = async () => {
8 | const { host, uid } = getEdlConfig()
9 |
10 | const { EDL_PASSWORD: password } = process.env
11 |
12 | const url = `${host}/oauth/token`
13 | const authorizationHeader = `Basic ${Buffer.from(`${uid}:${password}`).toString('base64')}`
14 |
15 | const response = await fetch(url, {
16 | method: 'POST',
17 | headers: {
18 | Authorization: authorizationHeader,
19 | 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
20 | },
21 | body: 'grant_type=client_credentials'
22 | })
23 | const json = await response.json()
24 |
25 | const accessToken = json.access_token
26 |
27 | return accessToken
28 | }
29 |
30 | export default fetchEdlClientToken
31 |
--------------------------------------------------------------------------------
/serverless/src/utils/getS3Client.js:
--------------------------------------------------------------------------------
1 | import { S3Client } from '@aws-sdk/client-s3'
2 |
3 | /**
4 | * Returns an S3 Client instance
5 | */
6 | export const getS3Client = () => {
7 | const config = {
8 | forcePathStyle: true
9 | }
10 |
11 | if (process.env.IS_OFFLINE) {
12 | config.endpoint = 'http://localhost:4569'
13 | config.credentials = {
14 | accessKeyId: 'S3RVER', // This specific key is required when working offline
15 | secretAccessKey: 'S3RVER'
16 | }
17 | }
18 |
19 | return new S3Client(config)
20 | }
21 |
--------------------------------------------------------------------------------
/serverless/src/utils/s3ListObjects.js:
--------------------------------------------------------------------------------
1 | import { ListObjectsV2Command } from '@aws-sdk/client-s3'
2 |
3 | /**
4 | * Returns list of contents from an S3 ListObjectsV2Command
5 | * @param {Object} s3Client S3 Client instance
6 | * @param {String} prefix Prefix used to search S3
7 | */
8 | export const s3ListObjects = async (s3Client, prefix) => {
9 | const { COLLECTION_TEMPLATES_BUCKET_NAME: collectionTemplatesBucketName } = process.env
10 |
11 | const s3Command = new ListObjectsV2Command({
12 | Bucket: collectionTemplatesBucketName,
13 | Prefix: prefix
14 | })
15 |
16 | const response = await s3Client.send(s3Command)
17 | const { Contents: s3Contents = [] } = response
18 |
19 | return s3Contents
20 | }
21 |
--------------------------------------------------------------------------------
/setup/graphQlProxy.js:
--------------------------------------------------------------------------------
1 | const http = require('http')
2 | const httpProxy = require('http-proxy')
3 |
4 | const proxy = httpProxy.createProxyServer({})
5 |
6 | // Create a proxy server to redirect path based CMR requests to the correct local CMR ports
7 | const server = http.createServer((req, res) => {
8 | // `req.url` here is the path of the requested URL
9 |
10 | if (req.url.includes('/search')) {
11 | const [, rest] = req.url.split('/search')
12 |
13 | // Replace the url value with everything after /search
14 | req.url = rest
15 | proxy.web(req, res, {
16 | target: 'http://localhost:3003'
17 | })
18 | }
19 |
20 | if (req.url.includes('/ingest')) {
21 | const [, rest] = req.url.split('/ingest')
22 |
23 | // Replace the url value with everything after /ingest
24 | req.url = rest
25 | proxy.web(req, res, {
26 | target: 'http://localhost:3002'
27 | })
28 | }
29 |
30 | if (req.url.includes('/access-control')) {
31 | const [, rest] = req.url.split('/access-control')
32 |
33 | // Replace the url value with everything after /access-control
34 | req.url = rest
35 | proxy.web(req, res, {
36 | target: 'http://localhost:3011'
37 | })
38 | }
39 | })
40 |
41 | console.log('listening on port 4000')
42 | server.listen(4000)
43 |
--------------------------------------------------------------------------------
/setup/resetCmr.js:
--------------------------------------------------------------------------------
1 | const resetCmr = async () => {
2 | await fetch('http://localhost:2999/reset', {
3 | method: 'POST'
4 | }).then(() => console.log('CMR Data Reset'))
5 | }
6 |
7 | resetCmr()
8 |
--------------------------------------------------------------------------------
/setup/stopCmr.js:
--------------------------------------------------------------------------------
1 | const { exec } = require('child_process')
2 |
3 | const stopCmr = () => {
4 | const command = 'date && echo "Stopping applications" && (curl -s -XPOST http://localhost:2999/stop; true)'
5 |
6 | exec(command, (error, stdout, stderr) => {
7 | if (error) {
8 | console.log(`error: ${error.message}`)
9 |
10 | return
11 | }
12 |
13 | if (stderr) {
14 | console.log(`stderr: ${stderr}`)
15 |
16 | return
17 | }
18 |
19 | console.log(`stdout: ${stdout}`)
20 | })
21 | }
22 |
23 | stopCmr()
24 |
--------------------------------------------------------------------------------
/sharedConstants/mmtCookie.js:
--------------------------------------------------------------------------------
1 | import { getApplicationConfig } from '../sharedUtils/getConfig'
2 |
3 | /**
4 | * This is the name of the cookie that MMT uses.
5 | */
6 | const { env } = getApplicationConfig()
7 |
8 | const MMT_COOKIE = `_mmt_jwt_${env}`
9 |
10 | export default MMT_COOKIE
11 |
--------------------------------------------------------------------------------
/sharedUtils/__tests__/getConfig.test.js:
--------------------------------------------------------------------------------
1 | import { getApplicationConfig, getUmmVersionsConfig } from '../getConfig'
2 |
3 | describe('getConfig', () => {
4 | describe('when applicationConfig is called', () => {
5 | test('returns a valid json object for applicationConfig', () => {
6 | const expectedApplicationConfig = {
7 | apiHost: 'http://localhost:4001/dev',
8 | graphQlHost: 'http://localhost:3013/api',
9 | cmrHost: 'http://localhost:4000',
10 | version: 'development'
11 | }
12 |
13 | const applicationConfig = getApplicationConfig()
14 |
15 | expect(applicationConfig).toMatchObject(expectedApplicationConfig)
16 | })
17 | })
18 |
19 | describe('when ummVersionConfig is called', () => {
20 | test('returns a valid json object for ummVersionConfig', () => {
21 | const expectedUmmVersionConfig = {
22 | ummC: '1.18.4',
23 | ummS: '1.5.3',
24 | ummT: '1.2.0',
25 | ummV: '1.9.0'
26 | }
27 |
28 | const ummVersionConfig = getUmmVersionsConfig()
29 |
30 | expect(ummVersionConfig).toMatchObject(expectedUmmVersionConfig)
31 | })
32 | })
33 | })
34 |
--------------------------------------------------------------------------------
/sharedUtils/getConfig.js:
--------------------------------------------------------------------------------
1 | import staticConfig from '../static.config.json'
2 |
3 | const getConfig = () => staticConfig
4 |
5 | export const getApplicationConfig = () => getConfig().application
6 | export const getUmmVersionsConfig = () => getConfig().ummVersions
7 | export const getSamlConfig = () => getConfig().saml
8 | export const getEdlConfig = () => getConfig().edl
9 |
--------------------------------------------------------------------------------
/static/src/assets/images/yellow-sea-swirls.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nasa/mmt/e5f1f442ffe5aef13beb703008625b4d103c8f1c/static/src/assets/images/yellow-sea-swirls.jpg
--------------------------------------------------------------------------------
/static/src/css/eui/index.scss:
--------------------------------------------------------------------------------
1 | @import 'nested-item-picker';
--------------------------------------------------------------------------------
/static/src/css/index.scss:
--------------------------------------------------------------------------------
1 | @import 'vendor';
2 | @import 'eui';
--------------------------------------------------------------------------------
/static/src/css/vendor/bootstrap/overrides/_navbar.scss:
--------------------------------------------------------------------------------
1 | .navbar {
2 | line-height: 1;
3 | }
4 |
5 | .navbar-brand {
6 | font-size: 1rem;
7 | font-weight: 100;
8 |
9 | &.nasa {
10 | padding-left: 2.75rem;
11 | background: url("@/assets/images/logos/nasa-meatball-new.svg") no-repeat;
12 | background-position: left center;
13 | background-size: 2.5rem 2.5rem;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/static/src/css/vendor/bootstrap/overrides/_table.scss:
--------------------------------------------------------------------------------
1 | @use "sass:map";
2 |
3 | @import '../variables';
4 |
5 | .table > :not(caption) > * > * {
6 | padding: 0.625rem 1rem;
7 | }
8 |
9 | .table > thead > tr > th {
10 | color: map.get($theme-colors, "secondary");
11 | font-weight: 800;
12 | }
13 |
14 | .table > tbody > tr > td {
15 | vertical-align: middle;
16 | }
17 |
18 |
--------------------------------------------------------------------------------
/static/src/css/vendor/bootstrap/overrides/index.scss:
--------------------------------------------------------------------------------
1 | @import 'navbar';
2 | @import 'table';
3 |
--------------------------------------------------------------------------------
/static/src/css/vendor/index.scss:
--------------------------------------------------------------------------------
1 | @layer framework {
2 | @import 'bootstrap';
3 | }
--------------------------------------------------------------------------------
/static/src/js/components/AuthCallback/AuthCallback.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Navigate } from 'react-router'
3 | import { useSearchParams } from 'react-router-dom'
4 |
5 | import useAuthContext from '@/js/hooks/useAuthContext'
6 | import isTokenExpired from '@/js/utils/isTokenExpired'
7 |
8 | /**
9 | * This class handles the authenticated redirect from our saml lambda function.
10 | * We get the launchpad token and redirect to the specified `target` path
11 | */
12 | export const AuthCallback = () => {
13 | const [searchParams] = useSearchParams()
14 |
15 | const { authLoading, tokenExpires } = useAuthContext()
16 |
17 | const target = searchParams.get('target')
18 |
19 | // If we are still loading the token from the cookie, don't Navigate yet
20 | if (authLoading) return null
21 |
22 | const isExpired = isTokenExpired(tokenExpires)
23 |
24 | // If we have a good token, Navigate to the target location
25 | if (!isExpired) {
26 | return (
27 |
28 | )
29 | }
30 |
31 | // Default to sending the user to the home page
32 | return (
33 |
34 | )
35 | }
36 |
37 | export default AuthCallback
38 |
--------------------------------------------------------------------------------
/static/src/js/components/BoundingRectangleField/BoundingRectangleField.scss:
--------------------------------------------------------------------------------
1 | .bounding-rectangle-container {
2 | display: flex;
3 | flex-direction: column;
4 |
5 | .bounding-rectangle-north-row,
6 | .bounding-rectangle-south-row {
7 | display: flex;
8 | justify-content: space-around;
9 | }
10 |
11 | .bounding-rectangle-east-west-row {
12 | display: flex;
13 | justify-content: space-between;
14 | }
15 |
16 | .bounding-rectangle-coordinate-label {
17 | text-align: center;
18 | }
19 |
20 | .bounding-rectangle-coordinate {
21 | width: 200px;
22 | text-align: center;
23 | }
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/static/src/js/components/Button/Button.scss:
--------------------------------------------------------------------------------
1 | .button {
2 | &--naked {
3 | border: none;
4 | background-color: transparent;
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/static/src/js/components/CheckPermissions/CheckPermissions.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Navigate, Outlet } from 'react-router'
4 |
5 | import usePermissions from '@/js/hooks/usePermissions'
6 |
7 | /**
8 | * Redirects the user if they do not have the given permission
9 | * @param {Object} params Object containing permissions to check
10 | * @param {Array} params.systemGroup Array of permissions to check in the system object 'GROUP'
11 | */
12 | const CheckPermissions = ({
13 | systemGroup
14 | }) => {
15 | const { hasSystemGroup, loading } = usePermissions({
16 | systemGroup
17 | })
18 |
19 | if (loading) {
20 | return (
21 |
22 | )
23 | }
24 |
25 | if (systemGroup && hasSystemGroup) {
26 | return
27 | }
28 |
29 | return
30 | }
31 |
32 | CheckPermissions.defaultProps = {
33 | systemGroup: null
34 | }
35 |
36 | CheckPermissions.propTypes = {
37 | systemGroup: PropTypes.arrayOf(PropTypes.string)
38 | }
39 |
40 | export default CheckPermissions
41 |
--------------------------------------------------------------------------------
/static/src/js/components/CollectionSelector/CollectionSelector.scss:
--------------------------------------------------------------------------------
1 | @use "sass:map";
2 |
3 | @import '../../../css/vendor/bootstrap/variables';
4 |
5 | .collection-selector {
6 | &__list-group {
7 | height:31.25rem;
8 | }
9 |
10 | &__list-group-item {
11 | cursor: pointer;
12 |
13 | &--selected {
14 | background-color: $gray-300;
15 | color: $gray-700;
16 | pointer-events: none;
17 | }
18 |
19 | &--available {
20 | background-color: $blue-100;
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/static/src/js/components/CustomArrayFieldTemplate/CustomArrayFieldTemplate.scss:
--------------------------------------------------------------------------------
1 | @use "sass:map";
2 |
3 | @import '../../../css/vendor/bootstrap/variables';
4 |
5 | .custom-array-field-template {
6 | &__title {
7 | display: flex;
8 | align-items: center;
9 | font-weight: 700;
10 | }
11 |
12 | &__field {
13 | display: flex;
14 | align-items: center;
15 | justify-content: space-between;
16 | }
17 |
18 | &__field-title {
19 | font-weight: 700;
20 | }
21 |
22 | &__element {
23 | padding: 1.25rem 1.5rem;
24 | border-left: 4px solid $gray-400;
25 | margin-bottom: 1rem;
26 |
27 | &:last-of-type {
28 | margin-bottom: 0;
29 | }
30 |
31 | &:has(*:focus) {
32 | border-left: 4px solid map.get($theme-colors, "blue-light");
33 | }
34 | }
35 |
36 | .custom-field-template {
37 | margin-bottom: 0;
38 | }
39 | }
40 |
41 | legend {
42 | display: none
43 | }
--------------------------------------------------------------------------------
/static/src/js/components/CustomTextWidget/CustomTextWidget.scss:
--------------------------------------------------------------------------------
1 | .custom-text-widget {
2 | &__input {
3 | min-width: 100%;
4 | height: 37px;
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/static/src/js/components/CustomTextareaWidget/CustomTextareaWidget.scss:
--------------------------------------------------------------------------------
1 | .custom-textarea-widget {
2 | &__input {
3 | min-width: 100%;
4 | min-height: 100px;
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/static/src/js/components/CustomTitleField/CustomTitleField.scss:
--------------------------------------------------------------------------------
1 | .custom-title-field {
2 | &__h1-box {
3 | margin-bottom: 1.5rem;
4 | }
5 |
6 | &__h2-box {
7 | margin-bottom: 1.5rem;
8 | }
9 |
10 | &__heading {
11 | margin-top: 1rem;
12 | font-weight: 700;
13 | }
14 |
15 | &__icon {
16 | font-size: 1rem;
17 | }
18 | }
--------------------------------------------------------------------------------
/static/src/js/components/CustomTitleFieldTemplate/CustomTitleFieldTemplate.scss:
--------------------------------------------------------------------------------
1 | .custom-title-field-template {
2 | &__heading {
3 | font-weight: 700;
4 | }
5 | }
--------------------------------------------------------------------------------
/static/src/js/components/CustomWidgetWrapper/CustomWidgetWrapper.scss:
--------------------------------------------------------------------------------
1 | @use "sass:map";
2 |
3 | @import '../../../css/vendor/bootstrap/variables';
4 |
5 | .custom-widget-wrapper {
6 | &__label {
7 | color: $gray-800;
8 | font-size: 0.925rem;
9 | font-weight: 600;
10 | }
11 |
12 | &__help {
13 | position: relative;
14 | right: -0.125rem;
15 | padding: 0.0625rem 0.25rem;
16 | border: 0;
17 | border-radius: 0.125rem;
18 | margin: 0;
19 | background-color: transparent;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/static/src/js/components/DraftPreview/DraftPreview.scss:
--------------------------------------------------------------------------------
1 | .draft-preview {
2 | &__preview {
3 | & > .container {
4 | max-width: initial;
5 | padding: 0
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/static/src/js/components/DraftPreviewPlaceholder/__tests__/DraftPreviewPlaceholder.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render, screen } from '@testing-library/react'
3 |
4 | import DraftPreviewPlaceholder from '../DraftPreviewPlaceholder'
5 |
6 | vi.mock('react-icons/fa')
7 | vi.mock('react-bootstrap/Placeholder', async () => ({
8 | default: vi.fn(() => (
9 |
10 | ))
11 | }))
12 |
13 | const setup = () => {
14 | const { container } = render(
15 |
16 | )
17 |
18 | return {
19 | container
20 | }
21 | }
22 |
23 | describe('DraftPreviewPlaceholder', () => {
24 | test('renders the placeholders', async () => {
25 | setup()
26 |
27 | const placeholders = await screen.findAllByTestId('placeholder')
28 | expect(placeholders.length).toBe(14)
29 | })
30 | })
31 |
--------------------------------------------------------------------------------
/static/src/js/components/ErrorPageNotFound/__tests__/ErrorPageNotFound.test.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | MemoryRouter,
3 | Route,
4 | Routes
5 | } from 'react-router-dom'
6 | import { render, screen } from '@testing-library/react'
7 | import React from 'react'
8 |
9 | import AuthContext from '@/js/context/AuthContext'
10 |
11 | import ErrorPageNotFound from '../ErrorPageNotFound'
12 |
13 | vi.mock('@/js/utils/errorLogger')
14 |
15 | const setup = () => {
16 | const context = {
17 | login: vi.fn()
18 | }
19 | render(
20 |
21 |
22 |
23 | } />
24 |
25 |
26 |
27 | )
28 |
29 | return {
30 | context
31 | }
32 | }
33 |
34 | describe('ErrorPageNotFound', () => {
35 | describe('when a user tries to navigate to a page that does not exist', () => {
36 | test('renders the 404 page', () => {
37 | setup()
38 |
39 | expect(screen.getByText('Sorry! The page you were looking for does not exist.')).toBeInTheDocument()
40 | expect(screen.getByText('mock-uuid')).toBeInTheDocument()
41 | })
42 | })
43 | })
44 |
--------------------------------------------------------------------------------
/static/src/js/components/Footer/Footer.scss:
--------------------------------------------------------------------------------
1 | @import '../../../css/vendor/bootstrap/variables';
2 |
3 | .footer {
4 | &__item {
5 | margin-left: 0.5rem;
6 |
7 |
8 | @include media-breakpoint-up(md) {
9 | &:not(:first-child) {
10 | &::before {
11 | margin-right: 1rem;
12 | color: var(--bs-white);
13 | content: "\b7";
14 | transform: scale(1.5);
15 | }
16 | }
17 | }
18 | }
19 |
20 | &__item-link {
21 | text-decoration: underline;
22 |
23 | &:hover {
24 | text-decoration: none;
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/static/src/js/components/For/__tests__/For.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render, screen } from '@testing-library/react'
3 |
4 | import For from '../For'
5 |
6 | describe('For component', () => {
7 | test('renders the example text', async () => {
8 | render(
9 |
12 | {
13 | (item) => (
14 |
15 | {item}
16 |
17 | )
18 | }
19 |
20 | )
21 |
22 | expect(screen.getByText('Item 1')).toBeInTheDocument()
23 | expect(screen.getByText('Item 2')).toBeInTheDocument()
24 | expect(screen.getByText('Item 3')).toBeInTheDocument()
25 | })
26 |
27 | test('passes the iteration as the second argument', async () => {
28 | render(
29 |
32 | {
33 | (item, i) => (
34 |
35 | {`${item} ${i + 1}`}
36 |
37 | )
38 | }
39 |
40 | )
41 |
42 | expect(screen.getByText('Item 1')).toBeInTheDocument()
43 | expect(screen.getByText('Item 2')).toBeInTheDocument()
44 | expect(screen.getByText('Item 3')).toBeInTheDocument()
45 | })
46 | })
47 |
--------------------------------------------------------------------------------
/static/src/js/components/FormNavigation/FormNavigation.scss:
--------------------------------------------------------------------------------
1 | .form-navigation {
2 | &__sections {
3 | --bs-list-group-bg: transparent;
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/static/src/js/components/GridLayout/GridLayout.scss:
--------------------------------------------------------------------------------
1 | .grid-layout {
2 | &__.description-box {
3 | margin-bottom: 1rem;
4 | }
5 |
6 | &__field-left-border {
7 | padding: 1.25rem 1.5rem;
8 | border-inline-Start: 4px solid #cad4d8
9 | }
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/static/src/js/components/GridRow/GridRow.scss:
--------------------------------------------------------------------------------
1 | .grid-row {
2 | &__group-description {
3 | margin-bottom: 2rem;
4 | color: #343a40;
5 | font-size: 0.925rem;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/static/src/js/components/Header/Header.scss:
--------------------------------------------------------------------------------
1 | @use "sass:map";
2 |
3 | @import '../../../css/vendor/bootstrap/variables';
4 |
5 | .header {
6 | &__brand-earthdata {
7 | margin-bottom: 0.25rem;
8 | font-size: 0.75rem;
9 | font-weight: 700;
10 | line-height: 1rem;
11 | }
12 |
13 | &__navbar-collapse {
14 | @include media-breakpoint-up(md) {
15 | max-width: 30rem;
16 | margin-left: 7rem;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/static/src/js/components/Header/__tests__/__mocks__/providerResults.js:
--------------------------------------------------------------------------------
1 | import { GET_PROVIDERS } from '../../../../operations/queries/getProviders'
2 |
3 | export const providerResults = {
4 | request: {
5 | query: GET_PROVIDERS,
6 | variables: {}
7 | },
8 | result: {
9 | data: {
10 | providers: {
11 | count: 3,
12 | items: [
13 | {
14 | providerId: 'TESTPROV',
15 | shortName: 'TESTPROV_SN'
16 | },
17 | {
18 | providerId: 'TESTPROV2',
19 | shortName: 'TESTPROV2_SN'
20 | },
21 | {
22 | providerId: 'TESTPROV3',
23 | shortName: 'TESTPROV3_SN'
24 | }
25 | ]
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/static/src/js/components/InstrumentField/InstrumentField.scss:
--------------------------------------------------------------------------------
1 | input[type='text'] {
2 | border-radius: 5px;
3 | }
4 |
5 | .instrument-field-select-title {
6 | margin-left: 8px;
7 | font-size: 1.2rem;
8 | font-weight: bold;
9 | }
10 |
11 | .instrument-field-select-option,
12 | .instrument-field-clear-option {
13 | padding: 0;
14 | border: none;
15 | margin: 0;
16 | margin-top: 0;
17 | background: none;
18 | color: inherit;
19 | cursor: pointer;
20 | font: inherit;
21 | font-size: 1.1rem;
22 | }
23 |
24 | .instrument-field-select-option {
25 | display: block;
26 | padding-left: 20px;
27 | }
28 |
29 | .instrument-field-clear-option {
30 | margin-left: 8px;
31 | list-style: none;
32 | }
33 |
34 | .instrument-field-text-field {
35 | display: flex;
36 | min-width: 100%;
37 | height: 40px;
38 | justify-content: space-between;
39 | padding-left: 8px;
40 | border-radius: 5px;
41 | margin-top: 5px;
42 | margin-bottom: 10px;
43 | }
44 |
45 | .instrument-field-select-option:hover,
46 | .instrument-field-clear-option:hover {
47 | background-color: #EBF2F6;
48 | }
49 |
--------------------------------------------------------------------------------
/static/src/js/components/JsonFileUploadModal/JsonFileUploadModal.scss:
--------------------------------------------------------------------------------
1 | .file-upload-area {
2 | --bs-border-style: dashed;
3 |
4 | cursor: pointer;
5 | }
6 |
--------------------------------------------------------------------------------
/static/src/js/components/JsonPreview/JsonPreview.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Accordion from 'react-bootstrap/Accordion'
3 | import JSONPretty from 'react-json-pretty'
4 | import { cloneDeep } from 'lodash-es'
5 |
6 | import useAppContext from '../../hooks/useAppContext'
7 | import removeEmpty from '../../utils/removeEmpty'
8 |
9 | const JsonPreview = () => {
10 | const {
11 | draft = {}
12 | } = useAppContext()
13 |
14 | // Remove || {} in MMT-4070
15 | const { ummMetadata = {} } = draft || {}
16 |
17 | const data = cloneDeep(removeEmpty(ummMetadata))
18 |
19 | return (
20 |
24 |
25 |
26 | JSON
27 |
28 |
29 |
30 |
31 |
32 |
33 | )
34 | }
35 |
36 | export default JsonPreview
37 |
--------------------------------------------------------------------------------
/static/src/js/components/KeywordForm/KeywordForm.scss:
--------------------------------------------------------------------------------
1 | .keyword-form {
2 | &__save-button {
3 | margin-block-end: 100px;
4 | }
5 | }
--------------------------------------------------------------------------------
/static/src/js/components/KeywordPicker/KeywordPicker.scss:
--------------------------------------------------------------------------------
1 | .rbt-menu>.dropdown-item {
2 | white-space: pre-wrap;
3 | }
4 |
5 | .rbt-menu>.dropdown-item::after {
6 | display: none;
7 | }
8 |
9 | .keyword-picker {
10 | margin-block-end: 25px;
11 |
12 | &__search-keywords {
13 | border-style: hidden;
14 | margin: 0;
15 | }
16 | }
17 |
18 | .rbt-aux .rbt-close {
19 | padding: 0;
20 | border: none;
21 | background-color: transparent;
22 | }
23 |
24 | .rbt-close-content::after {
25 | display: none;
26 | }
27 |
--------------------------------------------------------------------------------
/static/src/js/components/KeywordRecommendationsKeyword/KeywordRecommendationsKeyword.scss:
--------------------------------------------------------------------------------
1 | @use "sass:map";
2 |
3 | @import '../../../css/vendor/bootstrap/variables';
4 |
5 | .keyword-recommendations-keyword {
6 | &__keyword {
7 | font-size: 0.925em;
8 | font-weight: 700;
9 | }
10 |
11 | &__add-icon {
12 | color: $primary;
13 | cursor: ‘pointer’;
14 | font-size: 1.2em !important;
15 | }
16 |
17 | &__remove-icon {
18 | color: $danger;
19 | cursor: ‘pointer’;
20 | font-size: 1.2em !important;
21 | }
22 |
23 | &__accept-icon {
24 | color: $success;
25 | font-size: 1.2em !important;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/static/src/js/components/KeywordTreeContextMenu/KeywordTreeContextMenu.scss:
--------------------------------------------------------------------------------
1 | @use "sass:map";
2 |
3 | @import '../../../css/vendor/bootstrap/variables';
4 |
5 | .keyword-tree__context-menu {
6 | position: absolute;
7 | z-index: 1000;
8 | border: 1px solid $gray-300;
9 | background: $white;
10 | box-shadow: 2px 2px 5px rgb(0 0 0 / 10%);
11 |
12 | &-item {
13 | width: 100%;
14 | padding: 5px 10px;
15 | border: none;
16 | background: none;
17 | cursor: pointer;
18 | text-align: left;
19 | transition: background-color 0.2s ease;
20 |
21 | &:hover {
22 | background-color: rgb(204 229 255 / 100%);
23 | }
24 |
25 | &.focused {
26 | background-color: $blue-200;
27 | }
28 |
29 | &.hovered {
30 | background-color: $blue-100;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/static/src/js/components/KeywordTreePlaceHolder/KeywordTreePlaceHolder.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | import './KeywordTreePlaceHolder.scss'
5 | /**
6 | * KeywordTreePlaceHolder Component
7 | *
8 | * This component renders a placeholder message for the keyword tree.
9 | * It's typically used when the keyword tree is empty or loading.
10 | *
11 | * @param {Object} props - The component props
12 | * @param {string} props.message - The message to display in the placeholder
13 | * @returns {React.Element} A div containing the placeholder message
14 | */
15 | export const KeywordTreePlaceHolder = ({ message }) => (
16 |
17 | {message}
18 |
19 | )
20 |
21 | KeywordTreePlaceHolder.propTypes = {
22 | message: PropTypes.string.isRequired
23 | }
24 |
--------------------------------------------------------------------------------
/static/src/js/components/KeywordTreePlaceHolder/KeywordTreePlaceHolder.scss:
--------------------------------------------------------------------------------
1 | @use "sass:map";
2 |
3 | @import '../../../css/vendor/bootstrap/variables';
4 |
5 | .keyword-tree-placeholder {
6 | display: flex;
7 | width: 100%;
8 | height: 300px;
9 | align-items: center;
10 | justify-content: center;
11 | border: 1px solid $gray-300;
12 | border-radius: 4px;
13 | }
--------------------------------------------------------------------------------
/static/src/js/components/KeywordTreePlaceHolder/__tests__/KeywordTreePlaceHolder.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render, screen } from '@testing-library/react'
3 | import {
4 | describe,
5 | test,
6 | expect
7 | } from 'vitest'
8 | import { KeywordTreePlaceHolder } from '../KeywordTreePlaceHolder'
9 |
10 | describe('KeywordTreePlaceholder component', () => {
11 | describe('when rendering', () => {
12 | test('should display the provided message', () => {
13 | const testMessage = 'Test placeholder message'
14 | render()
15 |
16 | const placeholderElement = screen.getByText(testMessage)
17 | expect(placeholderElement).toBeTruthy()
18 | expect(placeholderElement).toHaveClass('keyword-tree-placeholder')
19 | })
20 |
21 | test('should apply correct CSS classes', () => {
22 | render()
23 |
24 | const placeholderElement = screen.getByText('Test message')
25 | expect(placeholderElement).toHaveClass('keyword-tree-placeholder')
26 | })
27 | })
28 | })
29 |
--------------------------------------------------------------------------------
/static/src/js/components/KmsConceptSelectionEditModal/KmsConceptSelectionEditModal.scss:
--------------------------------------------------------------------------------
1 | @use "sass:map";
2 |
3 | @import '../../../css/vendor/bootstrap/variables';
4 |
5 | .kms-concept-selection-edit-modal {
6 | &__search-input {
7 | padding: 8px;
8 | border: 1px solid #ccc;
9 | border-radius: 4px;
10 | inline-size: 80%;
11 |
12 | &:focus {
13 | border-color: #007bff;
14 | outline: none;
15 | }
16 | }
17 |
18 | &__scheme-selector {
19 | display: flex;
20 | align-items: center;
21 | padding-block-end: 8px;
22 | }
23 |
24 | &__label {
25 | font-weight: bold;
26 | padding-inline-end: 8px;
27 | }
28 |
29 | &__tree-wrapper {
30 | display: flex;
31 | align-items: center;
32 | margin-block-end: 12px;
33 | }
34 |
35 | &__tree-placeholder {
36 | display: flex;
37 | align-items: center;
38 | justify-content: center;
39 | border: 1px solid $gray-300;
40 | border-radius: 4px;
41 | block-size: 300px;
42 | inline-size: 100%;
43 | }
44 |
45 | &__apply-button {
46 | margin-inline-start: 10px;
47 | }
48 | }
--------------------------------------------------------------------------------
/static/src/js/components/KmsConceptSelectionWidget/KmsConceptSelectionWidget.scss:
--------------------------------------------------------------------------------
1 | .kms-concept-selection-widget {
2 | &__container {
3 | display: inline-flex;
4 | align-items: center;
5 | line-height: 1;
6 | }
7 |
8 | &__label {
9 | color: #333;
10 | font-size: 14px;
11 | margin-inline-end: 8px;
12 | }
13 |
14 | &__edit-button {
15 | display: inline-flex;
16 | align-items: center;
17 | border: none;
18 | margin: 0;
19 | background: none;
20 | color: #007bff;
21 | cursor: pointer;
22 | padding-inline-start: 5px;
23 | transition: color 0.2s ease-in-out;
24 |
25 | &:hover,
26 | &:focus {
27 | color: #0056b3;
28 | }
29 | }
30 |
31 | &__input {
32 | padding: 8px;
33 | border: 1px solid #ccc;
34 | border-radius: 4px;
35 | inline-size: 80%;
36 | transition: border-color 0.2s ease;
37 |
38 | &:focus {
39 | border-color: #007bff;
40 | outline: none;
41 | }
42 | }
43 |
44 | &__compact-alert {
45 | padding: 0.5rem 1rem;
46 | font-size: 0.875rem;
47 | margin-block-end: 0.5rem;
48 | }
49 | }
--------------------------------------------------------------------------------
/static/src/js/components/Layout/Layout.scss:
--------------------------------------------------------------------------------
1 | @use "sass:map";
2 |
3 | @import '../../../css/vendor/bootstrap/variables';
4 |
5 | .layout {
6 | &__sidebar {
7 | height: auto;
8 |
9 | @include media-breakpoint-up(md) {
10 | width: 18rem;
11 | height: 100%;
12 | }
13 | }
14 |
15 | &__brand-earthdata {
16 | margin-bottom: 0.25rem;
17 | font-size: 0.75rem;
18 | font-weight: 700;
19 | line-height: 0.75rem;
20 | }
21 |
22 | &__user-dropdown-item {
23 | color: $gray-700;
24 |
25 | &:hover,
26 | &:focus {
27 | background-color: $gray-300;
28 | color: $gray-800;
29 | }
30 |
31 | &:active {
32 | background-color: $gray-300;
33 | color: $gray-500;
34 | }
35 | }
36 |
37 | &__env-badge {
38 | top: 0.75rem;
39 | right: 0.75rem;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/static/src/js/components/LoadingBanner/LoadingBanner.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | import Col from 'react-bootstrap/Col'
5 | import Row from 'react-bootstrap/Row'
6 | import Spinner from 'react-bootstrap/Spinner'
7 |
8 | /**
9 | * LoadingBanner
10 | * @typedef {Object} LoadingBanner
11 | * @property {String} dataTestId A test id for the page
12 | */
13 |
14 | /**
15 | * Renders a Loading Banner
16 | * @param {LoadingBanner} props
17 | */
18 | export const LoadingBanner = ({
19 | dataTestId
20 | }) => (
21 |
22 |
23 |
28 |
29 |
30 | )
31 |
32 | LoadingBanner.propTypes = {
33 | dataTestId: PropTypes.string
34 | }
35 |
36 | LoadingBanner.defaultProps = {
37 | dataTestId: 'loading-banner__spinner'
38 | }
39 |
40 | export default LoadingBanner
41 |
--------------------------------------------------------------------------------
/static/src/js/components/LoadingBanner/__tests__/LoadingBanner.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { render } from '@testing-library/react'
4 |
5 | import { LoadingBanner } from '../LoadingBanner'
6 |
7 | describe('Error Banner Component', () => {
8 | describe('Provided a message', () => {
9 | test('it renders a loading banner', () => {
10 | const { container } = render(
11 |
12 | )
13 |
14 | // Match the snapshot
15 | expect(container).toMatchSnapshot()
16 | })
17 | })
18 |
19 | describe('Provided a test id', () => {
20 | test('it renders a loading banner', () => {
21 | const { container } = render(
22 |
23 | )
24 |
25 | // Match the snapshot
26 | expect(container).toMatchSnapshot()
27 | })
28 | })
29 | })
30 |
--------------------------------------------------------------------------------
/static/src/js/components/LoadingBanner/__tests__/__snapshots__/LoadingBanner.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Error Banner Component Provided a message it renders a loading banner 1`] = `
4 |
18 | `;
19 |
20 | exports[`Error Banner Component Provided a test id it renders a loading banner 1`] = `
21 |
35 | `;
36 |
--------------------------------------------------------------------------------
/static/src/js/components/LoadingBanner/__tests__/__snapshots__/LoadingBanner.test.jsx.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`Error Banner Component > Provided a message > it renders a loading banner 1`] = `
4 |
18 | `;
19 |
20 | exports[`Error Banner Component > Provided a test id > it renders a loading banner 1`] = `
21 |
35 | `;
36 |
--------------------------------------------------------------------------------
/static/src/js/components/LoadingTable/__tests__/LoadingTable.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render, screen } from '@testing-library/react'
3 | import LoadingTable from '../LoadingTable'
4 |
5 | const setup = () => {
6 | render(
7 |
8 | )
9 | }
10 |
11 | describe('LoadingTable', () => {
12 | test('should render table', async () => {
13 | setup()
14 | const table = screen.getByRole('table')
15 | expect(table).toBeInTheDocument()
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/static/src/js/components/MetadataForm/MetadataForm.scss:
--------------------------------------------------------------------------------
1 | @use "sass:map";
2 |
3 | @import '../../../css/vendor/bootstrap/variables';
4 |
5 | .metadata-form {
6 | &__container {
7 | .eui-icon {
8 | font-size: 1rem;
9 | vertical-align: baseline;
10 | }
11 | }
12 | }
13 |
14 | .rjsf {
15 | .custom-array-field-template__description,
16 | .field-description {
17 | margin-top: 0.5rem;
18 | margin-bottom: 0.5rem;
19 | color: $gray-800;
20 | font-size: 0.925rem;
21 | }
22 |
23 | .row.row-children {
24 | &:last-child {
25 | margin-bottom: 0;
26 | }
27 | }
28 |
29 | .col-md-12 {
30 | margin-bottom: 1rem;
31 |
32 | &:last-child {
33 | margin-bottom: 0;
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/static/src/js/components/MetadataPreviewPlaceholder/__tests__/MetadataPreviewPlaceholder.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from '@testing-library/react'
3 | import Placeholder from 'react-bootstrap/Placeholder'
4 |
5 | import MetadataPreviewPlaceholder from '../MetadataPreviewPlaceholder'
6 |
7 | vi.mock('react-bootstrap/Placeholder', () => ({
8 | // eslint-disable-next-line react/jsx-props-no-spreading
9 | default: vi.fn((props) => )
10 | }))
11 |
12 | const setup = () => {
13 | render(
14 |
15 | )
16 | }
17 |
18 | describe('MetadataPreviewPlaceholder', () => {
19 | test('renders the placeholders', () => {
20 | setup()
21 |
22 | expect(Placeholder).toHaveBeenCalledTimes(16)
23 | })
24 | })
25 |
--------------------------------------------------------------------------------
/static/src/js/components/NavigationItem/NavigationItem.scss:
--------------------------------------------------------------------------------
1 | @use "sass:map";
2 |
3 | @import '../../../css/vendor/bootstrap/variables';
4 |
5 | .navigation-item {
6 | &:hover {
7 | background: $gray-300;
8 | }
9 |
10 | &--is-active {
11 | background: $gray-300;
12 | }
13 |
14 | &__item {
15 | background: transparent;
16 |
17 | &--is-active {
18 | border: none;
19 | border-bottom: 1px solid black;
20 | font-weight: bold;
21 | }
22 | }
23 |
24 | &__icon {
25 | &--not-started {
26 | color: var(--bs-gray);
27 | }
28 |
29 | &--error {
30 | color: var(--bs-danger);
31 | }
32 |
33 | &--pass {
34 | color: var(--bs-green);
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/static/src/js/components/NavigationItemError/NavigationItemError.scss:
--------------------------------------------------------------------------------
1 | @use "sass:map";
2 |
3 | @import '../../../css/vendor/bootstrap/variables';
4 |
5 |
6 | .navigation-item-error {
7 | &__item {
8 | background-color: transparent;
9 | color: $gray-700;
10 |
11 | &--isFocused {
12 | background-color: transparent;
13 | }
14 |
15 | &:hover {
16 | color: $gray-900;
17 | }
18 | }
19 |
20 | &__icon {
21 | // padding-right: 1rem;
22 |
23 | &--not-started {
24 | color: var(--bs-gray);
25 | }
26 |
27 | &--error {
28 | color: var(--bs-danger);
29 | }
30 |
31 | &--pass {
32 | color: var(--bs-green);
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/static/src/js/components/Notifications/Notifications.scss:
--------------------------------------------------------------------------------
1 | .notifications-list {
2 | max-inline-size: 90vw; // Limit the maximum width of notifications
3 |
4 | .toast {
5 | max-inline-size: 100%; // Allow toasts to take full width of the container
6 | }
7 |
8 | &__icon-background {
9 | flex-shrink: 0;
10 | border-radius: 50%;
11 | block-size: 2rem;
12 | inline-size: 2rem;
13 | }
14 |
15 | &__icon {
16 | font-size: 1.2rem;
17 | }
18 |
19 | &__message {
20 | flex: 1;
21 | min-inline-size: 0; // Ensures flexbox works correctly
22 | overflow-wrap: break-word; // Additional support for wrapping
23 | padding-inline-end: 0.5rem; // Add some space before the close button
24 | word-wrap: break-word; // Allows long words to break and wrap
25 | }
26 |
27 | &__close-button {
28 | flex-shrink: 0;
29 | align-self: flex-start; // Aligns the button to the top
30 | white-space: nowrap;
31 | }
32 |
33 | &__timer {
34 | --timer-duration: 3000ms;
35 |
36 | animation: timer var(--timer-duration) linear;
37 | background-color: var(--bs-gray-600);
38 | block-size: 0.325rem;
39 | inline-size: 0%;
40 | }
41 |
42 | @keyframes timer {
43 | 0% {
44 | inline-size: 0%;
45 | }
46 |
47 | 100% {
48 | inline-size: 100%;
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/static/src/js/components/Page/Page.scss:
--------------------------------------------------------------------------------
1 | @import '../../../css/vendor/bootstrap/variables';
2 |
3 | .page {
4 | overflow-y: scroll;
5 |
6 | @include media-breakpoint-up(md) {
7 | overflow-y: auto;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/static/src/js/components/Page/__tests__/Page.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render, screen } from '@testing-library/react'
3 |
4 | import Page from '../Page'
5 |
6 | describe('Page component', () => {
7 | test('renders the header and children component', async () => {
8 | render(
9 | Mock Header Component}>
10 | Mock Children component
11 |
12 | )
13 |
14 | expect(screen.getByText('Mock Header Component')).toBeInTheDocument()
15 | expect(screen.getByText('Mock Children component')).toBeInTheDocument()
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/static/src/js/components/Panel/Panel.scss:
--------------------------------------------------------------------------------
1 | .panel {
2 | &__content {
3 | --bs-bg-opacity: 0.8;
4 |
5 | text-shadow: 0 0 10px rgb(0 0 0 / 30%);
6 | }
7 | }
--------------------------------------------------------------------------------
/static/src/js/components/Panel/__tests__/Panel.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render, screen } from '@testing-library/react'
3 |
4 | import Panel from '../Panel'
5 |
6 | const setup = () => (
7 | render(
8 |
9 | This is the test panel content
10 |
11 | )
12 | )
13 |
14 | describe('Panel component', () => {
15 | test('displays the title', () => {
16 | setup()
17 |
18 | expect(screen.getByText('This is the test title')).toBeInTheDocument()
19 | })
20 |
21 | test('displays the correct content', () => {
22 | setup()
23 |
24 | expect(screen.getByText('This is the test title')).toBeInTheDocument()
25 | expect(screen.getByText('This is the test panel content')).toBeInTheDocument()
26 | })
27 | })
28 |
--------------------------------------------------------------------------------
/static/src/js/components/Permission/Permission.scss:
--------------------------------------------------------------------------------
1 | .permission {
2 | &__pass-circle {
3 | color: var(--bs-green);
4 | }
5 |
6 | &__invalid-circle {
7 | color: var(--bs-danger);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/static/src/js/components/PlatformField/PlatformField.scss:
--------------------------------------------------------------------------------
1 | input[type='text'] {
2 | border-radius: 5px;
3 | }
4 |
5 | .platform-field-select-title {
6 | margin-left: 8px;
7 | font-size: 1.2rem;
8 | font-weight: bold;
9 | }
10 |
11 | .platform-field-select-option,
12 | .platform-field-clear-option {
13 | padding: 0;
14 | border: none;
15 | margin: 0;
16 | margin-top: 0;
17 | background: none;
18 | color: inherit;
19 | cursor: pointer;
20 | font: inherit;
21 | font-size: 1.1rem;
22 | }
23 |
24 | .platform-field-select-option {
25 | display: block;
26 | padding-left: 20px;
27 | }
28 |
29 | .platform-field-clear-option {
30 | margin-left: 8px;
31 | list-style: none;
32 | }
33 |
34 | .platForm-field-text-field {
35 | display: flex;
36 | min-width: 100%;
37 | height: 40px;
38 | justify-content: space-between;
39 | padding-left: 8px;
40 | border-radius: 5px;
41 | margin-top: 5px;
42 | margin-bottom: 10px;
43 | }
44 |
45 | .platform-field-select-option:hover,
46 | .platform-field-clear-option:hover {
47 | background-color: #EBF2F6;
48 | }
49 |
--------------------------------------------------------------------------------
/static/src/js/components/PreviewMapTemplate/PreviewMapTemplate.scss:
--------------------------------------------------------------------------------
1 | $padding-value: 15px;
2 |
3 | .preview-map-template__icon-label {
4 | padding: $padding-value;
5 | }
6 |
--------------------------------------------------------------------------------
/static/src/js/components/PreviewProgress/PreviewProgress.scss:
--------------------------------------------------------------------------------
1 | .preview-progress {
2 | &__container {
3 | display: grid;
4 | // grid-template-columns: auto auto auto;
5 | grid-template-columns: 33% 33% 33%;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/static/src/js/components/PreviewProgress/__tests__/__mocks__/configuration.js:
--------------------------------------------------------------------------------
1 | const testConfiguration = [
2 | {
3 | displayName: 'Information',
4 | properties: [
5 | 'Name',
6 | 'LongName'
7 | ]
8 | },
9 | {
10 | displayName: 'Version',
11 | properties: [
12 | 'Version'
13 | ]
14 | },
15 | {
16 | displayName: 'Description',
17 | properties: [
18 | 'Description'
19 | ]
20 | },
21 | {
22 | displayName: 'RelatedURLs',
23 | properties: [
24 | 'RelatedURLs'
25 | ]
26 | }
27 | ]
28 |
29 | export default testConfiguration
30 |
--------------------------------------------------------------------------------
/static/src/js/components/PrimaryNavigation/PrimaryNavigation.scss:
--------------------------------------------------------------------------------
1 | .primary-navigation {
2 | &__group-item {
3 | &:hover {
4 | background-color: var(--bs-light-dark);
5 | cursor: pointer;
6 | transition: all 0.2s ease-in-out;
7 | }
8 | }
9 |
10 | &__item {
11 | --bs-border-color: transparent;
12 |
13 | &:hover {
14 | --bs-link-color-rgb: rgba(var(--bs-white) / 85%);
15 | --bs-text-opacity: 0.75;
16 | }
17 | }
18 |
19 | &__version {
20 | font-size: 0.75rem;
21 | }
22 |
23 | &__dropdown-button {
24 | opacity: 0.4;
25 |
26 | .primary-navigation__group-item:hover & {
27 | opacity: 0.7;
28 | transition: all 0.2s ease-in-out;
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/static/src/js/components/PrimaryNavigationLink/PrimaryNavigationLink.scss:
--------------------------------------------------------------------------------
1 | @use "sass:map";
2 |
3 | @import '../../../css/vendor/bootstrap/variables';
4 |
5 | .primary-navigation-link {
6 | &__link {
7 | color: $gray-700;
8 |
9 | &:hover,
10 | &:focus {
11 | color: $gray-900;
12 | }
13 |
14 | &--is-child {
15 | padding: 0;
16 | }
17 | }
18 |
19 | &__title {
20 | font-size: 0.925rem;
21 | letter-spacing: 0.02rem;
22 | }
23 | }
--------------------------------------------------------------------------------
/static/src/js/components/ProgressSection/ProgressSection.scss:
--------------------------------------------------------------------------------
1 | @use "sass:map";
2 |
3 | @import '../../../css/vendor/bootstrap/variables';
4 |
5 | .progress-section {
6 | &:hover {
7 | background-color: rgba(var(--bs-light-rgb));
8 | cursor: pointer;
9 | }
10 |
11 | &__section-circle {
12 | display: block;
13 | margin-right: 10px;
14 | float: left;
15 | font-size: 2em;
16 | }
17 |
18 | &__section-label {
19 | padding-left: 4px;
20 | cursor: pointer;
21 | font-family: sans-serif;
22 | font-size: 16px;
23 |
24 | .progress-section:hover & {
25 | color: #{map.get($theme-colors, "blue")}
26 | }
27 | }
28 |
29 | &__section-icon {
30 | &--pass-circle {
31 | // color: rgb(24, 156, 84)
32 | color: var(--bs-green);
33 | }
34 |
35 | &--invalid-circle {
36 | // color: rgb(24, 156, 84)
37 | color: var(--bs-green);
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/static/src/js/components/PublishPreview/PublishPreview.scss:
--------------------------------------------------------------------------------
1 | .publish-preview {
2 | &__preview {
3 | & > .container {
4 | max-width: initial;
5 | padding: 0
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/static/src/js/components/StreetAddressField/StreetAddressField.scss:
--------------------------------------------------------------------------------
1 | .street-address-field {
2 | &__.address-line {
3 | margin-top: 15px;
4 | margin-left: -15px;
5 | }
6 |
7 | &__.description-box {
8 | margin-bottom: 15px;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/static/src/js/components/TemplatePreview/TemplatePreview.scss:
--------------------------------------------------------------------------------
1 | .template-preview {
2 | &__preview {
3 | & > .container {
4 | max-width: initial;
5 | padding: 0
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/static/src/js/components/UniqueItemsArray/UniqueItemsArray.scss:
--------------------------------------------------------------------------------
1 | .unique-items-array {
2 | &__element {
3 | padding: 0.5rem 0;
4 | }
5 |
6 | &__options {
7 | display: flex;
8 | flex-flow: row wrap;
9 | gap: 1rem;
10 |
11 | .form-check {
12 | min-width: 100px;
13 | margin-bottom: 0;
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/static/src/js/constants/conceptIdTypes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Mapping of concept id prefixes to concept types
3 | */
4 | const conceptIdTypes = {
5 | C: 'Collection',
6 | CIT: 'Citation',
7 | O: 'OrderOption',
8 | S: 'Service',
9 | T: 'Tool',
10 | V: 'Variable',
11 | VIS: 'Visualization'
12 | }
13 |
14 | export default conceptIdTypes
15 |
--------------------------------------------------------------------------------
/static/src/js/constants/conceptTypeDraftQueries.js:
--------------------------------------------------------------------------------
1 | import { CITATION_DRAFT } from '../operations/queries/getCitationDraft'
2 | import { COLLECTION_DRAFT } from '../operations/queries/getCollectionDraft'
3 | import { SERVICE_DRAFT } from '../operations/queries/getServiceDraft'
4 | import { TOOL_DRAFT } from '../operations/queries/getToolDraft'
5 | import { VARIABLE_DRAFT } from '../operations/queries/getVariableDraft'
6 | import { VISUALIZATION_DRAFT } from '../operations/queries/getVisualizationDraft'
7 |
8 | const conceptTypeDraftQueries = {
9 | Citation: CITATION_DRAFT,
10 | Collection: COLLECTION_DRAFT,
11 | Service: SERVICE_DRAFT,
12 | Tool: TOOL_DRAFT,
13 | Variable: VARIABLE_DRAFT,
14 | Visualization: VISUALIZATION_DRAFT
15 | }
16 |
17 | export default conceptTypeDraftQueries
18 |
--------------------------------------------------------------------------------
/static/src/js/constants/conceptTypeDraftsQueries.js:
--------------------------------------------------------------------------------
1 | import { GET_CITATION_DRAFTS } from '../operations/queries/getCitationDrafts'
2 | import { GET_COLLECTION_DRAFTS } from '../operations/queries/getCollectionDrafts'
3 | import { GET_SERVICE_DRAFTS } from '../operations/queries/getServiceDrafts'
4 | import { GET_TOOL_DRAFTS } from '../operations/queries/getToolDrafts'
5 | import { GET_VARIABLE_DRAFTS } from '../operations/queries/getVariableDrafts'
6 | import { GET_VISUALIZATION_DRAFTS } from '../operations/queries/getVisualizationDrafts'
7 |
8 | const conceptTypeDraftsQueries = {
9 | Citation: GET_CITATION_DRAFTS,
10 | Collection: GET_COLLECTION_DRAFTS,
11 | Service: GET_SERVICE_DRAFTS,
12 | Tool: GET_TOOL_DRAFTS,
13 | Variable: GET_VARIABLE_DRAFTS,
14 | Visualization: GET_VISUALIZATION_DRAFTS
15 | }
16 |
17 | export default conceptTypeDraftsQueries
18 |
--------------------------------------------------------------------------------
/static/src/js/constants/conceptTypes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Mapping of concept id types
3 | */
4 | const conceptTypes = {
5 | Citations: 'Citations',
6 | Collection: 'Collection',
7 | Collections: 'Collections',
8 | Service: 'Service',
9 | Services: 'Services',
10 | Tool: 'Tool',
11 | Tools: 'Tools',
12 | Variable: 'Variable',
13 | Variables: 'Variables',
14 | Visualizations: 'Visualizations'
15 | }
16 |
17 | export default conceptTypes
18 |
--------------------------------------------------------------------------------
/static/src/js/constants/dateFormat.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Moment date format type
3 | * 2024-04-16T18:20:12.124Z toTuesday, April 16, 2024 6:20 PM
4 | */
5 | export const DATE_FORMAT = 'LLLL'
6 |
--------------------------------------------------------------------------------
/static/src/js/constants/deleteMutationTypes.js:
--------------------------------------------------------------------------------
1 | import { DELETE_CITATION } from '@/js/operations/mutations/deleteCitation'
2 | import { DELETE_COLLECTION } from '@/js/operations/mutations/deleteCollection'
3 | import { DELETE_SERVICE } from '@/js/operations/mutations/deleteService'
4 | import { DELETE_TOOL } from '@/js/operations/mutations/deleteTool'
5 | import { DELETE_VARIABLE } from '@/js/operations/mutations/deleteVariable'
6 | import { DELETE_VISUALIZATION } from '@/js/operations/mutations/deleteVisualization'
7 |
8 | const deleteMutationTypes = {
9 | Citation: DELETE_CITATION,
10 | Collection: DELETE_COLLECTION,
11 | Service: DELETE_SERVICE,
12 | Tool: DELETE_TOOL,
13 | Variable: DELETE_VARIABLE,
14 | Visualization: DELETE_VISUALIZATION
15 | }
16 |
17 | export default deleteMutationTypes
18 |
--------------------------------------------------------------------------------
/static/src/js/constants/draftConceptIdTypes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Mapping of draft concept id prefixes to concept types
3 | */
4 | const draftConceptIdTypes = {
5 | CD: 'Collection',
6 | CITD: 'Citation',
7 | SD: 'Service',
8 | TD: 'Tool',
9 | VD: 'Variable',
10 | VISD: 'Visualization'
11 | }
12 |
13 | export default draftConceptIdTypes
14 |
--------------------------------------------------------------------------------
/static/src/js/constants/keywordsActions.js:
--------------------------------------------------------------------------------
1 | const keywordsActions = {
2 | ADD_KEYWORDS_DATA: 'ADD_KEYWORDS_DATA'
3 | }
4 |
5 | export default keywordsActions
6 |
--------------------------------------------------------------------------------
/static/src/js/constants/notificationsActions.js:
--------------------------------------------------------------------------------
1 | const notificationsActions = {
2 | NOTIFICATIONS_ADD: 'NOTIFICATIONS_ADD',
3 | NOTIFICATIONS_HIDE: 'NOTIFICATIONS_HIDE',
4 | NOTIFICATION_REMOVE: 'NOTIFICATION_REMOVE'
5 | }
6 |
7 | export default notificationsActions
8 |
--------------------------------------------------------------------------------
/static/src/js/constants/processingLevel.js:
--------------------------------------------------------------------------------
1 | const processingLevel = [
2 | 'Not Provided',
3 | '0',
4 | '1',
5 | '1A',
6 | '1B',
7 | '1C',
8 | '1T',
9 | '2',
10 | '2A',
11 | '2B',
12 | '2G',
13 | '2P',
14 | '3',
15 | '4',
16 | 'NA'
17 | ]
18 | export default processingLevel
19 |
--------------------------------------------------------------------------------
/static/src/js/constants/progressCircleTypes.js:
--------------------------------------------------------------------------------
1 | const progressCircleTypes = {
2 | NotStarted: 'Not Started',
3 | Pass: 'Pass',
4 | Error: 'Error',
5 | Invalid: 'Invalid'
6 | }
7 |
8 | export default progressCircleTypes
9 |
--------------------------------------------------------------------------------
/static/src/js/constants/redirectsMap/redirectsMap.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Stores URLs from legacy code and redirects users to appropriate URLS in react MMT
3 | */
4 | const REDIRECTS = {
5 | '/': 'collections',
6 | collection_drafts: 'drafts/collections',
7 | manage_cmr: 'collections',
8 | manage_collections: 'collections',
9 | manage_services: 'services',
10 | manage_tools: 'tools',
11 | manage_variables: 'variables',
12 | service_drafts: 'drafts/services',
13 | tool_drafts: 'drafts/tools',
14 | variable_drafts: 'drafts/variables'
15 | }
16 |
17 | export default REDIRECTS
18 |
--------------------------------------------------------------------------------
/static/src/js/constants/restoreRevisionMutations.js:
--------------------------------------------------------------------------------
1 | import { RESTORE_CITATION_REVISION } from '../operations/mutations/restoreCitationRevision'
2 | import { RESTORE_COLLECTION_REVISION } from '../operations/mutations/restoreCollectionRevision'
3 | import { RESTORE_SERVICE_REVISION } from '../operations/mutations/restoreServiceRevision'
4 | import { RESTORE_TOOL_REVISION } from '../operations/mutations/restoreToolRevision'
5 | import { RESTORE_VARIABLE_REVISION } from '../operations/mutations/restoreVariableRevision'
6 | import {
7 | RESTORE_VISUALIZATION_REVISION
8 | } from '../operations/mutations/restoreVisualizationRevision'
9 |
10 | const restoreRevisionMutations = {
11 | Citation: RESTORE_CITATION_REVISION,
12 | Collection: RESTORE_COLLECTION_REVISION,
13 | Service: RESTORE_SERVICE_REVISION,
14 | Tool: RESTORE_TOOL_REVISION,
15 | Variable: RESTORE_VARIABLE_REVISION,
16 | Visualization: RESTORE_VISUALIZATION_REVISION
17 | }
18 |
19 | export default restoreRevisionMutations
20 |
--------------------------------------------------------------------------------
/static/src/js/constants/saveTypes.js:
--------------------------------------------------------------------------------
1 | const saveTypes = {
2 | save: 'SAVE',
3 | saveAndContinue: 'SAVE_AND_CONTINUE',
4 | saveAndPreview: 'SAVE_AND_PREVIEW',
5 | saveAndPublish: 'SAVE_AND_PUBLISH',
6 | saveAndCreateDraft: 'SAVE_AND_CREATE_DRAFT',
7 | submit: 'SUBMIT'
8 | }
9 |
10 | export default saveTypes
11 |
--------------------------------------------------------------------------------
/static/src/js/constants/saveTypesToHumanizedStringMap.js:
--------------------------------------------------------------------------------
1 | import saveTypes from '@/js/constants/saveTypes'
2 |
3 | const saveTypesToHumanizedStringMap = {
4 | [saveTypes.save]: 'Save',
5 | [saveTypes.saveAndContinue]: 'Save & Continue',
6 | [saveTypes.saveAndCreateDraft]: 'Save & Create Draft',
7 | [saveTypes.saveAndPreview]: 'Save & Preview',
8 | [saveTypes.saveAndPublish]: 'Save & Publish',
9 | [saveTypes.submit]: 'Submit'
10 | }
11 |
12 | export default saveTypesToHumanizedStringMap
13 |
--------------------------------------------------------------------------------
/static/src/js/constants/typeParamToHumanizedStringMap.js:
--------------------------------------------------------------------------------
1 | const typeParamToHumanizedStringMap = {
2 | citations: 'citation',
3 | collections: 'collection',
4 | services: 'service',
5 | tools: 'tool',
6 | variables: 'variable',
7 | visualizations: 'visualization'
8 | }
9 |
10 | export default typeParamToHumanizedStringMap
11 |
--------------------------------------------------------------------------------
/static/src/js/constants/urlTypes.js:
--------------------------------------------------------------------------------
1 | const urltypes = [
2 | ['DistributionURL', 'DOWNLOAD SOFTWARE', 'MOBILE APP'],
3 | ['DistributionURL', 'DOWNLOAD SOFTWARE', ''],
4 | ['DistributionURL', 'GOTO WEB TOOL', 'LIVE ACCESS SERVER (LAS)'],
5 | ['DistributionURL', 'GOTO WEB TOOL', 'MAP VIEWER'],
6 | ['DistributionURL', 'GOTO WEB TOOL', 'SIMPLE SUBSET WIZARD (SSW)'],
7 | ['DistributionURL', 'GOTO WEB TOOL', 'SUBSETTER'],
8 | ['DistributionURL', 'GOTO WEB TOOL', '']
9 | ]
10 |
11 | export default urltypes
12 |
--------------------------------------------------------------------------------
/static/src/js/constants/urlValueToConceptStringMap.js:
--------------------------------------------------------------------------------
1 | const urlValueTypeToConceptTypeStringMap = {
2 | citations: 'Citation',
3 | cmr: 'CMR',
4 | collections: 'Collection',
5 | services: 'Service',
6 | tools: 'Tool',
7 | variables: 'Variable',
8 | visualizations: 'Visualization'
9 | }
10 |
11 | export default urlValueTypeToConceptTypeStringMap
12 |
--------------------------------------------------------------------------------
/static/src/js/context/AppContext.js:
--------------------------------------------------------------------------------
1 | import { createContext } from 'react'
2 |
3 | const AppContext = createContext()
4 |
5 | export default AppContext
6 |
--------------------------------------------------------------------------------
/static/src/js/context/AuthContext.js:
--------------------------------------------------------------------------------
1 | import { createContext } from 'react'
2 |
3 | const AuthContext = createContext()
4 |
5 | export default AuthContext
6 |
--------------------------------------------------------------------------------
/static/src/js/context/NotificationsContext.js:
--------------------------------------------------------------------------------
1 | import { createContext } from 'react'
2 |
3 | const NotificationsContext = createContext()
4 |
5 | export default NotificationsContext
6 |
--------------------------------------------------------------------------------
/static/src/js/hooks/useAccessibleEvent.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Adds the onClick and onKeyDown props to a component, calling the provided
3 | * callback on click of enter/spacebar is pressed
4 | * @param {Function} callback Callback function for when the element is clicked or enter/spacebar is pressed
5 | */
6 | const useAccessibleEvent = (callback) => ({
7 | role: 'link',
8 | tabIndex: 0,
9 | onClick: (event) => {
10 | callback(event)
11 | },
12 | onKeyDown: (event) => {
13 | // If the enter key or the Spacebar key is pressed
14 | if (event.key === 'Enter' || event.key === ' ') {
15 | callback(event)
16 | }
17 | }
18 | })
19 |
20 | export default useAccessibleEvent
21 |
--------------------------------------------------------------------------------
/static/src/js/hooks/useAppContext.js:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react'
2 |
3 | import AppContext from '../context/AppContext'
4 |
5 | const useAppContext = () => useContext(AppContext)
6 |
7 | export default useAppContext
8 |
--------------------------------------------------------------------------------
/static/src/js/hooks/useAuthContext.js:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react'
2 |
3 | import AuthContext from '../context/AuthContext'
4 |
5 | const useAuthContext = () => useContext(AuthContext)
6 |
7 | export default useAuthContext
8 |
--------------------------------------------------------------------------------
/static/src/js/hooks/useKeywords.js:
--------------------------------------------------------------------------------
1 | import { useCallback, useReducer } from 'react'
2 |
3 | import { initialState, keywordsReducer } from '../reducers/keywordsReducer'
4 | import keywordsActions from '../constants/keywordsActions'
5 |
6 | const useKeywords = () => {
7 | const [keywords, keywordsDispatch] = useReducer(keywordsReducer, initialState)
8 |
9 | /**
10 | * Adds keyword data to the keywords reducer
11 | */
12 | const addKeywordsData = useCallback(({
13 | data,
14 | type
15 | }) => {
16 | keywordsDispatch({
17 | type: keywordsActions.ADD_KEYWORDS_DATA,
18 | payload: {
19 | type,
20 | data
21 | }
22 | })
23 | }, [])
24 |
25 | return {
26 | addKeywordsData,
27 | keywords
28 | }
29 | }
30 |
31 | export default useKeywords
32 |
--------------------------------------------------------------------------------
/static/src/js/hooks/useMMTCookie.js:
--------------------------------------------------------------------------------
1 | import { useCookies } from 'react-cookie'
2 |
3 | import MMT_COOKIE from 'sharedConstants/mmtCookie'
4 |
5 | /**
6 | * Returns the cookie value for the MMT auth cookie
7 | */
8 | const useMMTCookie = () => {
9 | const [
10 | cookies,
11 | setCookie,
12 | removeCookie
13 | ] = useCookies([MMT_COOKIE])
14 | const { [MMT_COOKIE]: mmtJwt } = cookies
15 |
16 | return {
17 | mmtJwt,
18 | setCookie,
19 | removeCookie
20 | }
21 | }
22 |
23 | export default useMMTCookie
24 |
--------------------------------------------------------------------------------
/static/src/js/hooks/useNotificationsContext.js:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react'
2 |
3 | import NotificationsContext from '../context/NotificationsContext'
4 |
5 | const useNotificationsContext = () => useContext(NotificationsContext)
6 |
7 | export default useNotificationsContext
8 |
--------------------------------------------------------------------------------
/static/src/js/operations/mutations/createAcl.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const CREATE_ACL = gql`
4 | mutation CreateAcl(
5 | $catalogItemIdentity: JSON,
6 | $groupPermissions: JSON!,
7 | $providerIdentity: JSON,
8 | $systemIdentity: JSON
9 | ) {
10 | createAcl(
11 | catalogItemIdentity: $catalogItemIdentity
12 | groupPermissions: $groupPermissions,
13 | providerIdentity: $providerIdentity,
14 | systemIdentity: $systemIdentity
15 | ) {
16 | revisionId
17 | conceptId
18 | }
19 | }`
20 |
--------------------------------------------------------------------------------
/static/src/js/operations/mutations/createAssociation.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const CREATE_ASSOCIATION = gql`
4 | mutation CreateAssociation (
5 | $conceptId: String!
6 | $associatedConceptIds: [String]
7 | $associatedConceptData: JSON
8 | ) {
9 | createAssociation (
10 | conceptId: $conceptId
11 | associatedConceptIds: $associatedConceptIds
12 | associatedConceptData: $associatedConceptData
13 | ) {
14 | associatedConceptId
15 | conceptId
16 | revisionId
17 | }
18 | }
19 | `
20 |
--------------------------------------------------------------------------------
/static/src/js/operations/mutations/createGroup.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const CREATE_GROUP = gql`
4 | mutation CreateGroup (
5 | $description: String
6 | $members: String
7 | $name: String!
8 | $tag: String
9 | ) {
10 | createGroup (
11 | description: $description
12 | members: $members
13 | name: $name
14 | tag: $tag
15 | ) {
16 | description
17 | id
18 | name
19 | tag
20 | }
21 | }
22 | `
23 |
24 | // Example Variables:
25 | // {
26 | // "name": "Test Group",
27 | // "tag": "MMT_2",
28 | // "description": "Test group",
29 | // "members": ""
30 | // }
31 |
--------------------------------------------------------------------------------
/static/src/js/operations/mutations/createOrderOption.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const CREATE_ORDER_OPTION = gql`
4 | mutation CreateOrderOption (
5 | $providerId: String!
6 | $description: String
7 | $form: String
8 | $nativeId: String
9 | $name: String
10 | $scope: OrderOptionScopeType
11 | $sortKey: String
12 | ) {
13 | createOrderOption (
14 | providerId: $providerId
15 | description: $description
16 | form: $form
17 | nativeId: $nativeId
18 | name: $name
19 | scope: $scope
20 | sortKey: $sortKey
21 | ) {
22 | conceptId
23 | revisionId
24 | }
25 | }
26 | `
27 | // Example Variables:
28 | // {
29 | // "providerId": "MMT_2",
30 | // "description": "Order Option Description",
31 | // "form": "<><>",
32 | // "nativeId": "MMT_1234-abcd-5678",
33 | // "name": "Order Option 1",
34 | // "scope": "PROVIDER",
35 | // }
36 |
--------------------------------------------------------------------------------
/static/src/js/operations/mutations/deleteAcl.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const DELETE_ACL = gql`
4 | mutation DeleteAcl($conceptId: String!) {
5 | deleteAcl(conceptId: $conceptId) {
6 | conceptId
7 | revisionId
8 | }
9 | }`
10 |
--------------------------------------------------------------------------------
/static/src/js/operations/mutations/deleteAssociation.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const DELETE_ASSOCIATION = gql`
4 | mutation DeleteAssociation(
5 | $conceptId: String!
6 | $associatedConceptIds: [String]
7 | ) {
8 | deleteAssociation(
9 | conceptId: $conceptId
10 | associatedConceptIds: $associatedConceptIds
11 | ) {
12 | revisionId
13 | conceptId
14 | associatedConceptId
15 | }
16 | }
17 | `
18 |
--------------------------------------------------------------------------------
/static/src/js/operations/mutations/deleteCitation.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const DELETE_CITATION = gql`
4 | mutation DeleteCitation (
5 | $providerId: String!
6 | $nativeId: String!
7 | ) {
8 | deleteCitation (
9 | providerId: $providerId
10 | nativeId: $nativeId
11 | ) {
12 | conceptId
13 | revisionId
14 | }
15 | }
16 | `
17 |
--------------------------------------------------------------------------------
/static/src/js/operations/mutations/deleteCollection.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const DELETE_COLLECTION = gql`
4 | mutation DeleteCollection (
5 | $providerId: String!
6 | $nativeId: String!
7 | ) {
8 | deleteCollection (
9 | providerId: $providerId
10 | nativeId: $nativeId
11 | ) {
12 | conceptId
13 | revisionId
14 | }
15 | }
16 | `
17 |
18 | // Example Variables:
19 | // {
20 | // "conceptId": "VC1200000096-MMT_2",",
21 | // "providerId": "MMT_2"
22 | // }
23 |
--------------------------------------------------------------------------------
/static/src/js/operations/mutations/deleteDraft.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | // Query to delete a draft
4 | export const DELETE_DRAFT = gql`
5 | mutation DeleteDraft (
6 | $conceptType: DraftConceptType!
7 | $nativeId: String!
8 | $providerId: String!
9 | ) {
10 | deleteDraft (
11 | conceptType: $conceptType
12 | nativeId: $nativeId
13 | providerId: $providerId
14 | ) {
15 | conceptId
16 | revisionId
17 | }
18 | }
19 | `
20 |
21 | // Example Variables:
22 | // {
23 | // "conceptType": "Tool",
24 | // "nativeId": "tool-3",
25 | // "providerId": "MMT_2"
26 | // }
27 |
--------------------------------------------------------------------------------
/static/src/js/operations/mutations/deleteGroup.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const DELETE_GROUP = gql`
4 | mutation DeleteGroup (
5 | $id: String!
6 | ) {
7 | deleteGroup (
8 | id: $id
9 | )
10 | }
11 | `
12 |
--------------------------------------------------------------------------------
/static/src/js/operations/mutations/deleteOrderOption.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const DELETE_ORDER_OPTION = gql`
4 | mutation DeleteOrderOption (
5 | $nativeId: String!
6 | $providerId: String!
7 | ) {
8 | deleteOrderOption (
9 | nativeId: $nativeId
10 | providerId: $providerId
11 | ) {
12 | conceptId
13 | revisionId
14 | }
15 | }
16 | `
17 | // Example Variables:
18 | // {
19 | // "nativeId": "1234-abcd-5678-efgh",
20 | // "providerId": MMT_2,
21 | // }
22 |
--------------------------------------------------------------------------------
/static/src/js/operations/mutations/deleteService.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const DELETE_SERVICE = gql`
4 | mutation DeleteService (
5 | $providerId: String!
6 | $nativeId: String!
7 | ) {
8 | deleteService (
9 | providerId: $providerId
10 | nativeId: $nativeId
11 | ) {
12 | conceptId
13 | revisionId
14 | }
15 | }
16 | `
17 |
18 | // Example Variables:
19 | // {
20 | // "conceptId": "S1200000096-MMT_2",",
21 | // "providerId": "MMT_2"
22 | // }
23 |
--------------------------------------------------------------------------------
/static/src/js/operations/mutations/deleteTool.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const DELETE_TOOL = gql`
4 | mutation DeleteTool (
5 | $providerId: String!
6 | $nativeId: String!
7 | ) {
8 | deleteTool (
9 | providerId: $providerId
10 | nativeId: $nativeId
11 | ) {
12 | conceptId
13 | revisionId
14 | }
15 | }
16 | `
17 |
18 | // Example Variables:
19 | // {
20 | // "nativeId": "T1200000096-MMT_2",",
21 | // "providerId": "MMT_2"
22 | // }
23 |
--------------------------------------------------------------------------------
/static/src/js/operations/mutations/deleteVariable.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const DELETE_VARIABLE = gql`
4 | mutation DeleteVariable (
5 | $providerId: String!
6 | $nativeId: String!
7 | ) {
8 | deleteVariable (
9 | providerId: $providerId
10 | nativeId: $nativeId
11 | ) {
12 | conceptId
13 | revisionId
14 | }
15 | }
16 | `
17 |
18 | // Example Variables:
19 | // {
20 | // "conceptId": "V1200000096-MMT_2",",
21 | // "providerId": "MMT_2"
22 | // }
23 |
--------------------------------------------------------------------------------
/static/src/js/operations/mutations/deleteVisualization.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const DELETE_VISUALIZATION = gql`
4 | mutation DeleteVisualization (
5 | $providerId: String!
6 | $nativeId: String!
7 | ) {
8 | deleteVisualization (
9 | providerId: $providerId
10 | nativeId: $nativeId
11 | ) {
12 | conceptId
13 | revisionId
14 | }
15 | }
16 | `
17 |
18 | // Example Visualizations:
19 | // {
20 | // "conceptId": "V1S200000096-MMT_2",",
21 | // "providerId": "MMT_2"
22 | // }
23 |
--------------------------------------------------------------------------------
/static/src/js/operations/mutations/ingestDraft.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | // Query to delete a draft
4 | export const INGEST_DRAFT = gql`
5 | mutation IngestDraft (
6 | $conceptType: DraftConceptType!
7 | $metadata: JSON!
8 | $nativeId: String!
9 | $providerId: String!
10 | $ummVersion: String!
11 | ) {
12 | ingestDraft (
13 | conceptType: $conceptType
14 | metadata: $metadata
15 | nativeId: $nativeId
16 | providerId: $providerId
17 | ummVersion: $ummVersion
18 | ) {
19 | conceptId
20 | revisionId
21 | }
22 | }
23 | `
24 |
25 | // Example Variables:
26 | // {
27 | // "conceptType": "Tool",
28 | // "metadata": {},
29 | // "nativeId": "tool-0",
30 | // "providerId": "MMT_2",
31 | // "ummVersion": "1.0.0"
32 | // }
33 |
--------------------------------------------------------------------------------
/static/src/js/operations/mutations/publishDraft.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const PUBLISH_DRAFT = gql`
4 | mutation PublishDraft (
5 | $draftConceptId: String!
6 | $nativeId: String!
7 | $ummVersion: String!
8 | ) {
9 | publishDraft (
10 | draftConceptId: $draftConceptId
11 | nativeId: $nativeId
12 | ummVersion: $ummVersion
13 | ) {
14 | conceptId
15 | revisionId
16 | }
17 | }
18 | `
19 | // Example Variables:
20 | // {
21 | // "draftConceptId": "TD1200000-MMT_2",
22 | // "nativeId": "publish native id",
23 | // "ummVersion": "1.0.0"
24 | // }
25 |
--------------------------------------------------------------------------------
/static/src/js/operations/mutations/restoreCitationRevision.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const RESTORE_CITATION_REVISION = gql`
4 | mutation RestoreCitationRevision (
5 | $conceptId: String!
6 | $revisionId: String!
7 | ) {
8 | restoreCitationRevision (
9 | conceptId: $conceptId
10 | revisionId: $revisionId
11 | ) {
12 | conceptId
13 | revisionId
14 | }
15 | }
16 | `
17 | // Example Variables:
18 | // {
19 | // "conceptId": "CIT1200000098-MMT_2",
20 | // "revisionId": "1"
21 | // }
22 |
--------------------------------------------------------------------------------
/static/src/js/operations/mutations/restoreCollectionRevision.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const RESTORE_COLLECTION_REVISION = gql`
4 | mutation RestoreCollectionRevision (
5 | $conceptId: String!
6 | $revisionId: String!
7 | ) {
8 | restoreCollectionRevision (
9 | conceptId: $conceptId
10 | revisionId: $revisionId
11 | ) {
12 | conceptId
13 | revisionId
14 | }
15 | }
16 | `
17 | // Example Variables:
18 | // {
19 | // "conceptId": "C1200000098-MMT_2",
20 | // "revisionId": "1"
21 | // }
22 |
--------------------------------------------------------------------------------
/static/src/js/operations/mutations/restoreServiceRevision.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const RESTORE_SERVICE_REVISION = gql`
4 | mutation RestoreServiceRevision (
5 | $conceptId: String!
6 | $revisionId: String!
7 | ) {
8 | restoreServiceRevision (
9 | conceptId: $conceptId
10 | revisionId: $revisionId
11 | ) {
12 | conceptId
13 | revisionId
14 | }
15 | }
16 | `
17 | // Example Variables:
18 | // {
19 | // "conceptId": "S1200000098-MMT_2",
20 | // "revisionId": "1"
21 | // }
22 |
--------------------------------------------------------------------------------
/static/src/js/operations/mutations/restoreToolRevision.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const RESTORE_TOOL_REVISION = gql`
4 | mutation RestoreToolRevision (
5 | $conceptId: String!
6 | $revisionId: String!
7 | ) {
8 | restoreToolRevision (
9 | conceptId: $conceptId
10 | revisionId: $revisionId
11 | ) {
12 | conceptId
13 | revisionId
14 | }
15 | }
16 | `
17 | // Example Variables:
18 | // {
19 | // "conceptId": "T1200000098-MMT_2",
20 | // "revisionId": "1"
21 | // }
22 |
--------------------------------------------------------------------------------
/static/src/js/operations/mutations/restoreVariableRevision.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const RESTORE_VARIABLE_REVISION = gql`
4 | mutation RestoreVariableRevision (
5 | $conceptId: String!
6 | $revisionId: String!
7 | ) {
8 | restoreVariableRevision (
9 | conceptId: $conceptId
10 | revisionId: $revisionId
11 | ) {
12 | conceptId
13 | revisionId
14 | }
15 | }
16 | `
17 | // Example Variables:
18 | // {
19 | // "conceptId": "V1200000098-MMT_2",
20 | // "revisionId": "1"
21 | // }
22 |
--------------------------------------------------------------------------------
/static/src/js/operations/mutations/restoreVisualizationRevision.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const RESTORE_VISUALIZATION_REVISION = gql`
4 | mutation RestoreVisualizationRevision (
5 | $conceptId: String!
6 | $revisionId: String!
7 | ) {
8 | restoreVisualizationRevision (
9 | conceptId: $conceptId
10 | revisionId: $revisionId
11 | ) {
12 | conceptId
13 | revisionId
14 | }
15 | }
16 | `
17 | // Example Variables:
18 | // {
19 | // "conceptId": "VIS1200000098-MMT_2",
20 | // "revisionId": "1"
21 | // }
22 |
--------------------------------------------------------------------------------
/static/src/js/operations/mutations/updateAcl.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const UPDATE_ACL = gql`
4 | mutation UpdateAcl(
5 | $catalogItemIdentity: JSON,
6 | $conceptId: String!,
7 | $groupPermissions: JSON,
8 | $providerIdentity: JSON,
9 | $systemIdentity: JSON
10 | ) {
11 | updateAcl(
12 | catalogItemIdentity: $catalogItemIdentity
13 | conceptId: $conceptId,
14 | groupPermissions: $groupPermissions,
15 | providerIdentity: $providerIdentity,
16 | systemIdentity: $systemIdentity
17 | ) {
18 | conceptId
19 | revisionId
20 | }
21 | }`
22 |
--------------------------------------------------------------------------------
/static/src/js/operations/mutations/updateGroup.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const UPDATE_GROUP = gql`
4 | mutation UpdateGroup (
5 | $id: String!
6 | $description: String
7 | $members: String
8 | $name: String
9 | $tag: String
10 | ) {
11 | updateGroup (
12 | id: $id
13 | description: $description
14 | members: $members
15 | name: $name
16 | tag: $tag
17 | ) {
18 | description
19 | id
20 | members {
21 | count
22 | items {
23 | id
24 | emailAddress
25 | }
26 | }
27 | name
28 | tag
29 | }
30 | }
31 | `
32 |
--------------------------------------------------------------------------------
/static/src/js/operations/mutations/updateOrderOption.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const UPDATE_ORDER_OPTION = gql`
4 | mutation UpdateOrderOption (
5 | $deprecated: Boolean
6 | $description: String
7 | $form: String
8 | $name: String
9 | $nativeId: String!
10 | $providerId: String!
11 | $scope: OrderOptionScopeType
12 | $sortKey: String
13 | ) {
14 | updateOrderOption (
15 | deprecated: $deprecated
16 | providerId: $providerId
17 | description: $description
18 | form: $form
19 | nativeId: $nativeId
20 | name: $name
21 | scope: $scope
22 | sortKey: $sortKey
23 | ) {
24 | conceptId
25 | revisionId
26 | }
27 | }
28 | `
29 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getAvailableProviders.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | // Query to available providers acls
4 | export const GET_AVAILABLE_PROVIDERS = gql`
5 | query GetAvailableProviders($params: AclsInput) {
6 | acls(params: $params) {
7 | items {
8 | conceptId
9 | providerIdentity
10 | }
11 | }
12 | }
13 | `
14 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getCitation.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const GET_CITATION = gql`
4 | query GetCitation ($params: CitationInput, $collectionsParams: CollectionsInput) {
5 | citation (params: $params) {
6 | abstract
7 | citationMetadata
8 | collections (params: $collectionsParams) {
9 | count
10 | items {
11 | conceptId
12 | provider
13 | shortName
14 | title
15 | version
16 | }
17 | }
18 | conceptId
19 | identifier
20 | identifierType
21 | name
22 | pageTitle: name
23 | nativeId
24 | providerId
25 | relatedIdentifiers
26 | resolutionAuthority
27 | revisionDate
28 | revisionId
29 | revisions {
30 | count
31 | items {
32 | conceptId
33 | revisionDate
34 | revisionId
35 | userId
36 | }
37 | }
38 | scienceKeywords
39 | ummMetadata
40 | userId
41 | }
42 | }
43 | `
44 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getCitationDraft.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const CITATION_DRAFT = gql`
4 | query CitationDraft($params: DraftInput) {
5 | draft(params: $params) {
6 | conceptId
7 | conceptType
8 | deleted
9 | name
10 | nativeId
11 | providerId
12 | revisionDate
13 | revisionId
14 | previewMetadata {
15 | ... on Citation {
16 | abstract
17 | citationMetadata
18 | conceptId
19 | identifier
20 | identifierType
21 | name
22 | pageTitle: name
23 | nativeId
24 | providerId
25 | relatedIdentifiers
26 | resolutionAuthority
27 | revisionDate
28 | revisionId
29 | scienceKeywords
30 | userId
31 | }
32 | }
33 | ummMetadata
34 | }
35 | }
36 | `
37 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getCitationDrafts.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | // Query to retrieve citation drafts for listing open drafts
4 | export const GET_CITATION_DRAFTS = gql`
5 | query CitationDrafts($params: DraftsInput) {
6 | drafts(params: $params) {
7 | count
8 | items {
9 | conceptId
10 | providerId
11 | revisionDate
12 | ummMetadata
13 | previewMetadata {
14 | ... on Citation {
15 | name
16 | }
17 | }
18 | }
19 | }
20 | }
21 | `
22 |
23 | // Example Variables:
24 | // {
25 | // "params": {
26 | // "conceptType": "Citation",
27 | // "limit": 5,
28 | // "provider": "MMT_2",
29 | // "sortKey": ["-revision_date"]
30 | // }
31 | // }
32 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getCitations.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const GET_CITATIONS = gql`
4 | query GetCitation($params: CitationsInput) {
5 | citations(params: $params) {
6 | count
7 | items {
8 | conceptId
9 | revisionId
10 | name
11 | providerId
12 | revisionDate
13 | citationMetadata
14 | }
15 | }
16 | }
17 | `
18 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getCollectionDrafts.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | // Query to retrieve collection drafts for listing open drafts
4 | export const GET_COLLECTION_DRAFTS = gql`
5 | query CollectionDrafts($params: DraftsInput) {
6 | drafts(params: $params) {
7 | count
8 | items {
9 | providerId
10 | conceptId
11 | revisionDate
12 | ummMetadata
13 | }
14 | }
15 | }
16 | `
17 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getCollectionForPermissionForm.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const GET_COLLECTION_FOR_PERMISSION_FORM = gql`
4 | query GetCollectionForPermissionForm($conceptId: String!, $params: CollectionsInput) {
5 | acl(conceptId: $conceptId) {
6 | catalogItemIdentity {
7 | providerId
8 | collectionApplicable
9 | collectionIdentifier
10 | granuleApplicable
11 | granuleIdentifier
12 | }
13 | groups {
14 | items {
15 | id
16 | name
17 | permissions
18 | tag
19 | userType
20 | }
21 | }
22 | collections(params: $params) {
23 | count
24 | items {
25 | conceptId,
26 | shortName,
27 | provider,
28 | directDistributionInformation,
29 | entryTitle,
30 | version
31 | }
32 | }
33 | conceptId
34 | name
35 | }
36 | }
37 | `
38 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getCollectionPermission.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const GET_COLLECTION_PERMISSION = gql`
4 | query GetCollectionPermission($conceptId: String!, $collectionParams: CollectionsInput) {
5 | acl(conceptId: $conceptId) {
6 | conceptId
7 | identityType
8 | location
9 | name
10 | providerIdentity
11 | revisionId
12 | systemIdentity
13 | catalogItemIdentity {
14 | collectionIdentifier
15 | collectionApplicable
16 | granuleApplicable
17 | granuleIdentifier
18 | }
19 | collections (params: $collectionParams) {
20 | count
21 | items {
22 | conceptId
23 | shortName
24 | title
25 | version
26 | }
27 | }
28 | groups {
29 | items {
30 | permissions
31 | userType
32 | id
33 | name
34 | }
35 | }
36 | }
37 | }
38 | `
39 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getCollectionPermissions.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const GET_COLLECTION_PERMISSIONS = gql`
4 | query GetCollectionPermissions($params: AclsInput) {
5 | acls(params: $params) {
6 | count
7 | items {
8 | name
9 | conceptId
10 | catalogItemIdentity {
11 | providerId
12 | }
13 | }
14 | }
15 | }
16 | `
17 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getCollectionRevisions.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const GET_COLLECTION_REVISIONS = gql`
4 | query GetCollectionRevisions($params: CollectionsInput) {
5 | collections(params: $params) {
6 | count
7 | items {
8 | conceptId
9 | shortName
10 | version
11 | title
12 | revisionId
13 | revisionDate
14 | userId
15 | }
16 | }
17 | }
18 | `
19 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getCollections.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const GET_COLLECTIONS = gql`
4 | query GetCollections($params: CollectionsInput) {
5 | collections(params: $params) {
6 | count
7 | items {
8 | conceptId
9 | shortName
10 | version
11 | title
12 | provider
13 | entryTitle
14 | revisionId
15 | granules {
16 | count
17 | }
18 | tagDefinitions {
19 | items {
20 | tagKey
21 | description
22 | }
23 | }
24 | tags
25 | revisionDate
26 | }
27 | }
28 | }
29 | `
30 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getGroup.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const GET_GROUP = gql`
4 | query Group (
5 | $params: GroupInput
6 | ) {
7 | group (
8 | params: $params
9 | ) {
10 | description
11 | id
12 | name
13 | members {
14 | count
15 | items {
16 | id
17 | firstName
18 | lastName
19 | emailAddress
20 | }
21 | }
22 | tag
23 | }
24 | }
25 | `
26 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getGroupAcls.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const GET_GROUP_ACLS = gql`
4 | query GetGroupAcls (
5 | $params: GroupInput,
6 | $aclParams: GroupAclsInput
7 | ) {
8 | group (
9 | params: $params
10 | ) {
11 | id
12 | acls(params: $aclParams) {
13 | count
14 | items {
15 | name
16 | catalogItemIdentity {
17 | providerId
18 | }
19 | conceptId
20 | }
21 | }
22 | }
23 | }
24 | `
25 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getGroups.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const GET_GROUPS = gql`
4 | query Groups (
5 | $params: GroupsInput
6 | ) {
7 | groups (
8 | params: $params
9 | ) {
10 | count
11 | items {
12 | description
13 | id
14 | members {
15 | count
16 | }
17 | name
18 | tag
19 | }
20 | }
21 | }
22 | `
23 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getGroupsForPermissionSelect.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const GET_GROUPS_FOR_PERMISSION_SELECT = gql`
4 | query GetGroupsForPermissionSelect (
5 | $params: GroupsInput
6 | ) {
7 | groups (
8 | params: $params
9 | ) {
10 | items {
11 | id
12 | name
13 | tag
14 | }
15 | }
16 | }
17 | `
18 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getOrderOption.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const GET_ORDER_OPTION = gql`
4 | query GetOrderOption($params: OrderOptionInput, $collectionsParams: CollectionsInput) {
5 | orderOption(params: $params) {
6 | associationDetails
7 | conceptId
8 | deprecated
9 | description
10 | form
11 | name
12 | nativeId
13 | pageTitle: name
14 | providerId
15 | revisionId
16 | revisionDate
17 | scope
18 | sortKey
19 | collections (params: $collectionsParams) {
20 | count
21 | items {
22 | title
23 | conceptId
24 | entryTitle
25 | shortName
26 | version
27 | provider
28 | }
29 | }
30 | }
31 | }
32 | `
33 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getOrderOptions.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const GET_ORDER_OPTIONS = gql`
4 | query GetOrderOptions($params: OrderOptionsInput) {
5 | orderOptions(params: $params) {
6 | count
7 | items {
8 | description
9 | deprecated
10 | conceptId
11 | name
12 | form
13 | nativeId
14 | providerId
15 | scope
16 | sortKey
17 | associationDetails
18 | revisionDate
19 | }
20 | }
21 | }
22 | `
23 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getPermissionCollections.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const GET_PERMISSION_COLLECTIONS = gql`
4 | query GetPermissionCollection($params: CollectionsInput) {
5 | collections(params: $params) {
6 | items {
7 | conceptId
8 | directDistributionInformation
9 | shortName
10 | provider
11 | entryTitle
12 | }
13 | }
14 | }
15 | `
16 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getPermissions.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const GET_PERMISSIONS = gql`
4 | query Permissions(
5 | $groupPermissionParams: PermissionsInput
6 | $keywordsPermissionParams: PermissionsInput
7 | # $otherParams: PermissionsInput
8 | ) {
9 | groupPermissions: permissions(params: $groupPermissionParams) {
10 | count
11 | items {
12 | systemObject
13 | permissions
14 | }
15 | }
16 | keywordsPermissions: permissions(params: $keywordsPermissionParams) {
17 | count
18 | items {
19 | systemObject
20 | permissions
21 | }
22 | }
23 |
24 | # Example: can add more permissions checks into this query and pass separate params in the future if we need to
25 | # otherPermissions: permissions(params: $otherParams) {
26 | # count
27 | # items {
28 | # systemObject
29 | # permissions
30 | # target
31 | # }
32 | # }
33 |
34 | # Alternatively, use the acls query to return all permissions for a user
35 | }
36 | `
37 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getProviderIdentityPermissions.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const GET_PROVIDER_IDENTITY_PERMISSIONS = gql`
4 | query GetProviderIdentityPermissions($params: AclsInput) {
5 | acls(params: $params) {
6 | items {
7 | conceptId
8 | groups {
9 | items {
10 | id
11 | permissions
12 | userType
13 | }
14 | }
15 | providerIdentity
16 | }
17 | }
18 | }
19 | `
20 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getProviders.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const GET_PROVIDERS = gql`
4 | query Providers {
5 | providers {
6 | count
7 | items {
8 | providerId
9 | shortName
10 | }
11 | }
12 | }
13 | `
14 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getServiceAssociations.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const GET_SERVICE_ASSOCIATIONS = gql`
4 | query GetServiceAssociations ($params: CollectionInput) {
5 | collection (params: $params) {
6 | conceptId
7 | shortName
8 | services {
9 | count
10 | items {
11 | conceptId
12 | longName
13 | orderOptions {
14 | count
15 | items {
16 | name
17 | conceptId
18 | }
19 | }
20 | }
21 | }
22 | }
23 | }
24 | `
25 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getServiceDrafts.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | // Query to retrieve service drafts for listing open drafts
4 | export const GET_SERVICE_DRAFTS = gql`
5 | query ServiceDrafts($params: DraftsInput) {
6 | drafts(params: $params) {
7 | count
8 | items {
9 | conceptId
10 | providerId
11 | revisionDate
12 | ummMetadata
13 | previewMetadata {
14 | ... on Service {
15 | name
16 | longName
17 | }
18 | }
19 | }
20 | }
21 | }
22 | `
23 |
24 | // Example Variables:
25 | // {
26 | // "params": {
27 | // "conceptType": "Service",
28 | // "limit": 5,
29 | // "provider": "MMT_2",
30 | // "sortKey": ["-revision_date"]
31 | // }
32 | // }
33 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getServices.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const GET_SERVICES = gql`
4 | query GetServices($params: ServicesInput) {
5 | services(params: $params) {
6 | count
7 | items {
8 | conceptId
9 | name
10 | longName
11 | providerId
12 | revisionDate
13 | revisionId
14 | userId
15 | }
16 | }
17 | }
18 | `
19 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getSystemIdentityPermissions.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const GET_SYSTEM_IDENTITY_PERMISSIONS = gql`
4 | query GetSystemIdentityPermissions($params: AclsInput) {
5 | acls(params: $params) {
6 | items {
7 | conceptId
8 | groups {
9 | items {
10 | id
11 | permissions
12 | userType
13 | }
14 | }
15 | systemIdentity
16 | }
17 | }
18 | }
19 | `
20 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getToolDrafts.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | // Query to retrieve tool drafts for listing open drafts
4 | export const GET_TOOL_DRAFTS = gql`
5 | query ToolDrafts($params: DraftsInput) {
6 | drafts(params: $params) {
7 | count
8 | items {
9 | conceptId
10 | providerId
11 | revisionDate
12 | ummMetadata
13 | previewMetadata {
14 | ... on Tool {
15 | name
16 | longName
17 | }
18 | }
19 | }
20 | }
21 | }
22 | `
23 |
24 | // Example Variables:
25 | // {
26 | // "params": {
27 | // "conceptType": "Tool",
28 | // "limit": 5,
29 | // "provider": "MMT_2",
30 | // "sortKey": ["-revision_date"]
31 | // }
32 | // }
33 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getTools.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const GET_TOOLS = gql`
4 | query GetTools($params: ToolsInput) {
5 | tools(params: $params) {
6 | count
7 | items {
8 | conceptId
9 | name
10 | longName
11 | providerId
12 | revisionId
13 | revisionDate
14 | revisionId
15 | userId
16 | }
17 | }
18 | }
19 | `
20 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getVariableDrafts.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | // Query to retrieve variable drafts for listing open drafts
4 | export const GET_VARIABLE_DRAFTS = gql`
5 | query VariableDrafts($params: DraftsInput) {
6 | drafts(params: $params) {
7 | count
8 | items {
9 | conceptId
10 | providerId
11 | revisionDate
12 | ummMetadata
13 | previewMetadata {
14 | ... on Variable {
15 | name
16 | longName
17 | }
18 | }
19 | }
20 | }
21 | }
22 | `
23 |
24 | // Example Variables:
25 | // {
26 | // "params": {
27 | // "conceptType": "Variable",
28 | // "limit": 5,
29 | // "provider": "MMT_2",
30 | // "sortKey": ["-revision_date"]
31 | // }
32 | // }
33 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getVariables.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const GET_VARIABLES = gql`
4 | query GetVariables($params: VariablesInput) {
5 | variables(params: $params) {
6 | count
7 | items {
8 | conceptId
9 | name
10 | longName
11 | providerId
12 | revisionDate
13 | revisionId
14 | userId
15 | }
16 | }
17 | }
18 | `
19 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getVisualization.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const GET_VISUALIZATION = gql`
4 | query GetVisualization ($params: VisualizationInput, $collectionsParams: CollectionsInput) {
5 | visualization (params: $params) {
6 | conceptId
7 | description
8 | generation
9 | identifier
10 | metadataSpecification
11 | name
12 | pageTitle: name
13 | nativeId
14 | providerId
15 | revisionDate
16 | revisionId
17 | revisions {
18 | count
19 | items {
20 | conceptId
21 | revisionDate
22 | revisionId
23 | userId
24 | }
25 | }
26 | collections (params: $collectionsParams) {
27 | count
28 | items {
29 | conceptId
30 | provider
31 | shortName
32 | title
33 | version
34 | }
35 | }
36 | scienceKeywords
37 | spatialExtent
38 | specification
39 | subtitle
40 | temporalExtents
41 | title
42 | ummMetadata
43 | visualizationType
44 | }
45 | }
46 | `
47 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getVisualizationDraft.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const VISUALIZATION_DRAFT = gql`
4 | query VisualizationDraft($params: DraftInput) {
5 | draft(params: $params) {
6 | conceptId
7 | conceptType
8 | deleted
9 | name
10 | nativeId
11 | providerId
12 | revisionDate
13 | revisionId
14 | previewMetadata {
15 | ... on Visualization {
16 | conceptId
17 | description
18 | generation
19 | identifier
20 | metadataSpecification
21 | name,
22 | pageTitle: name
23 | nativeId
24 | providerId
25 | revisionDate
26 | revisionId
27 | scienceKeywords
28 | spatialExtent
29 | specification
30 | subtitle
31 | temporalExtents
32 | title
33 | ummMetadata
34 | visualizationType
35 | }
36 | }
37 | ummMetadata
38 | }
39 | }
40 | `
41 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getVisualizationDrafts.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const GET_VISUALIZATION_DRAFTS = gql`
4 | query VisualizationDrafts($params: DraftsInput) {
5 | drafts(params: $params) {
6 | count
7 | items {
8 | conceptId
9 | providerId
10 | revisionDate
11 | ummMetadata
12 | previewMetadata {
13 | ... on Visualization {
14 | conceptId
15 | name
16 | title
17 | }
18 | }
19 | }
20 | }
21 | }
22 | `
23 |
--------------------------------------------------------------------------------
/static/src/js/operations/queries/getVisualizations.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const GET_VISUALIZATIONS = gql`
4 | query GetVisualizations($params: VisualizationsInput) {
5 | visualizations(params: $params) {
6 | count
7 | items {
8 | conceptId
9 | revisionId
10 | name
11 | title
12 | providerId
13 | revisionDate
14 | }
15 | }
16 | }
17 | `
18 |
--------------------------------------------------------------------------------
/static/src/js/operations/utils/generateCreateAclMutation.js:
--------------------------------------------------------------------------------
1 | import { isNull, omitBy } from 'lodash-es'
2 |
3 | /**
4 | * Generates a mutation object for creating ACL (Access Control List) for a target with group permissions.
5 | *
6 | * @param {Array} groupPermissions - The group permissions for the ACL.
7 | * @param {String} target - The target for which the ACL is being created.
8 | * @returns {Object} - The mutation object.
9 | */
10 | const generateCreateMutation = (mutation, groupPermissions, identity) => ({
11 | variables: {
12 | groupPermissions: groupPermissions.map((gp) => omitBy({
13 | ...gp,
14 | group_id: gp.id,
15 | permissions: gp.permissions
16 | }, (value, key) => isNull(value) || ['id', '__typename'].includes(key))),
17 | ...identity
18 | },
19 | mutation
20 | })
21 |
22 | export default generateCreateMutation
23 |
--------------------------------------------------------------------------------
/static/src/js/operations/utils/generateDeleteAclMutation.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Generates a delete mutation for a given concept ID and target.
3 | *
4 | * @param {String} conceptId - The concept ID to be deleted.
5 | * @returns {Object} - The delete mutation object.
6 | */
7 | const generateDeleteMutation = (mutation, conceptId) => ({
8 | variables: {
9 | conceptId
10 | },
11 | mutation
12 | })
13 |
14 | export default generateDeleteMutation
15 |
--------------------------------------------------------------------------------
/static/src/js/operations/utils/generateUpdateAclMutation.js:
--------------------------------------------------------------------------------
1 | import { isNull, omitBy } from 'lodash-es'
2 |
3 | /**
4 | * Generates an update mutation object for updating ACL permissions.
5 | *
6 | * @param {String} conceptId - The concept ID.
7 | * @param {Array} groupPermissions - The group permissions.
8 | * @param {String} target - The target.
9 | * @returns {Object} - The update mutation object.
10 | */
11 | const generateUpdateMutation = (mutation, conceptId, groupPermissions, identity) => ({
12 | variables: {
13 | conceptId,
14 | groupPermissions: groupPermissions.map((gp) => omitBy({
15 | ...gp,
16 | group_id: gp.id,
17 | permissions: gp.permissions
18 | }, (value, key) => isNull(value) || ['id', '__typename'].includes(key))),
19 | ...identity
20 | },
21 | mutation
22 | })
23 |
24 | export default generateUpdateMutation
25 |
--------------------------------------------------------------------------------
/static/src/js/pages/HomePage/HomePage.scss:
--------------------------------------------------------------------------------
1 | .home-page {
2 | &__panel-content {
3 | --bs-bg-opacity: 0.8;
4 |
5 | text-shadow: 0 0 10px rgb(0 0 0 / 30%);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/static/src/js/pages/KeywordManagerPage/KeywordManagerPage.scss:
--------------------------------------------------------------------------------
1 | @use "sass:map";
2 |
3 | @import '../../../css/vendor/bootstrap/variables';
4 |
5 | .keyword-manager-page {
6 | &__content {
7 | display: flex;
8 | flex-wrap: wrap;
9 | }
10 |
11 | &__tree-container {
12 | overflow: auto;
13 | width: 40%;
14 | max-width: 40%;
15 | max-height: 80vh;
16 | align-self: flex-start;
17 | padding: 10px;
18 | border: 1px solid $gray-300;
19 | border-radius: 4px;
20 | }
21 |
22 | &__form-container {
23 | max-width: 58.33%;
24 | min-height: 300px;
25 | flex-basis: 58.33%;
26 | padding: 0 15px 0 40px;
27 | }
28 |
29 | &__selector-container {
30 | display: flex;
31 | align-items: center;
32 | margin-bottom: 20px;
33 | }
34 |
35 | &__selector-label {
36 | margin-right: 10px;
37 | margin-bottom: 25px;
38 | }
39 |
40 | &__scheme-selector-label {
41 | margin-right: 10px;
42 | margin-bottom: 25px;
43 | margin-left: 20px;
44 | }
45 |
46 | &__scheme-selector-wrapper {
47 | width: 300px;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/static/src/js/pages/LogoutPage/LogoutPage.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import { Navigate } from 'react-router'
3 |
4 | import useAuthContext from '@/js/hooks/useAuthContext'
5 |
6 | /**
7 | * Renders a `LogoutPage` component, this component will clear the cookie and navigate to the home page
8 | *
9 | * @component
10 | * @example Renders a `LogoutPage` component
11 | * return (
12 | *
13 | * )
14 | */
15 | const LogoutPage = () => {
16 | const { setToken, tokenValue } = useAuthContext()
17 |
18 | useEffect(() => {
19 | setToken(null)
20 | }, [])
21 |
22 | if (tokenValue) return null
23 |
24 | return (
25 |
26 | )
27 | }
28 |
29 | export default LogoutPage
30 |
--------------------------------------------------------------------------------
/static/src/js/pages/LogoutPage/__tests__/LogoutPage.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from '@testing-library/react'
3 | import { MemoryRouter, Navigate } from 'react-router'
4 |
5 | import AuthContext from '@/js/context/AuthContext'
6 |
7 | import LogoutPage from '../LogoutPage'
8 |
9 | vi.mock('react-router', async () => ({
10 | ...await vi.importActual('react-router'),
11 | Navigate: vi.fn()
12 | }))
13 |
14 | const setup = () => {
15 | vi.setSystemTime('2024-01-01')
16 |
17 | const context = {
18 | setToken: vi.fn()
19 | }
20 |
21 | render(
22 |
23 |
24 |
25 |
26 |
27 | )
28 |
29 | return { context }
30 | }
31 |
32 | describe('LogoutPage component', () => {
33 | test('calls setToken and Navigate', () => {
34 | const { context } = setup()
35 |
36 | expect(context.setToken).toHaveBeenCalledTimes(1)
37 | expect(context.setToken).toHaveBeenCalledWith(null)
38 |
39 | expect(Navigate).toHaveBeenCalledTimes(1)
40 | expect(Navigate).toHaveBeenCalledWith({
41 | to: '/',
42 | replace: true
43 | }, {})
44 | })
45 | })
46 |
--------------------------------------------------------------------------------
/static/src/js/pages/OrderOptionListPage/__tests__/OrderOptionListPage.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render, screen } from '@testing-library/react'
3 | import { BrowserRouter } from 'react-router-dom'
4 |
5 | import OrderOptionList from '../../../components/OrderOptionList/OrderOptionList'
6 | import OrderOptionListPage from '../OrderOptionListPage'
7 |
8 | vi.mock('../../../components/OrderOptionList/OrderOptionList')
9 |
10 | const setup = () => {
11 | render(
12 |
13 |
14 |
15 | )
16 | }
17 |
18 | describe('OrderOptionListPage', () => {
19 | describe('show order option page', () => {
20 | test('render the page and calls OrderOptionList', async () => {
21 | setup()
22 |
23 | expect(screen.getByRole('link', { name: 'Order Options' })).toBeInTheDocument()
24 | expect(screen.getByRole('heading', { name: 'Order Options' })).toBeInTheDocument()
25 | expect(OrderOptionList).toHaveBeenCalled(1)
26 | })
27 | })
28 | })
29 |
--------------------------------------------------------------------------------
/static/src/js/pages/PermissionListPage/__tests__/PermissionListPage.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render, screen } from '@testing-library/react'
3 |
4 | import PermissionList from '@/js/components/PermissionList/PermissionList'
5 | import { MemoryRouter } from 'react-router'
6 | import PermissionListPage from '../PermissionListPage'
7 |
8 | vi.mock('../../../components/PermissionList/PermissionList')
9 |
10 | const setup = () => {
11 | render(
12 |
13 |
14 |
15 | )
16 | }
17 |
18 | describe('PermissionListPage', () => {
19 | describe('show permission list page', () => {
20 | test('render the page and calls PermissionList', async () => {
21 | setup()
22 |
23 | expect(screen.getAllByText('Collection Permissions')[0]).toBeInTheDocument()
24 | expect(PermissionList).toHaveBeenCalled(1)
25 | })
26 | })
27 | })
28 |
--------------------------------------------------------------------------------
/static/src/js/pages/ProvidersPage/__tests__/ProvidersPage.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render, screen } from '@testing-library/react'
3 |
4 | import Providers from '@/js/components/Providers/Providers'
5 |
6 | import ProvidersPage from '../ProvidersPage'
7 |
8 | vi.mock('@/js/components/Providers/Providers').mockImplementation(() => {})
9 |
10 | const setup = () => {
11 | render(
12 |
13 | )
14 | }
15 |
16 | describe('ProvidersPage', () => {
17 | test('renders the page', async () => {
18 | setup()
19 |
20 | expect(await screen.findByRole('heading', { value: 'Mock group' })).toBeVisible()
21 |
22 | expect(Providers).toHaveBeenCalledTimes(1)
23 | })
24 | })
25 |
--------------------------------------------------------------------------------
/static/src/js/pages/SearchPage/__tests__/__mocks__/providerResults.js:
--------------------------------------------------------------------------------
1 | import { GET_PROVIDERS } from '@/js/operations/queries/getProviders'
2 |
3 | export const providerResults = {
4 | request: {
5 | query: GET_PROVIDERS,
6 | variables: {}
7 | },
8 | result: {
9 | data: {
10 | providers: {
11 | count: 3,
12 | items: [
13 | {
14 | providerId: 'TESTPROV',
15 | shortName: 'TESTPROV_SN'
16 | },
17 | {
18 | providerId: 'TESTPROV2',
19 | shortName: 'TESTPROV2_SN'
20 | },
21 | {
22 | providerId: 'TESTPROV3',
23 | shortName: 'TESTPROV3_SN'
24 | }
25 | ]
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/static/src/js/providers/withProviders/withProviders.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Providers from '../Providers/Providers'
3 |
4 | const withProviders = (WrappedComponent) => {
5 | const WithProviders = (props) => (
6 |
7 |
8 |
9 | )
10 |
11 | return WithProviders
12 | }
13 |
14 | export default withProviders
15 |
--------------------------------------------------------------------------------
/static/src/js/reducers/keywordsReducer.js:
--------------------------------------------------------------------------------
1 | import keywordsActions from '../constants/keywordsActions'
2 |
3 | export const initialState = {}
4 |
5 | // {
6 | // relatedUrls: {
7 | // isLoading: false,
8 | // isLoaded: false,
9 | // data: {}
10 | // }
11 | // }
12 |
13 | export const keywordsReducer = (state, action) => {
14 | switch (action.type) {
15 | case keywordsActions.ADD_KEYWORDS_DATA: {
16 | const { type, data } = action.payload
17 |
18 | return {
19 | ...state,
20 | [type]: {
21 | ...state[type],
22 | data
23 | }
24 | }
25 | }
26 |
27 | default:
28 | return state
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/static/src/js/schemas/group.js:
--------------------------------------------------------------------------------
1 | const group = {
2 | $schema: 'http://json-schema.org/draft-07/schema#',
3 | title: 'group',
4 | type: 'object',
5 | additionalProperties: false,
6 | properties: {
7 | name: {
8 | description: 'The name of the group.',
9 | type: 'string',
10 | minLength: 1,
11 | maxLength: 85
12 | },
13 | description: {
14 | description: 'The description of the group.',
15 | type: 'string',
16 | minLength: 1,
17 | maxLength: 255
18 | },
19 | members: {
20 | description: 'Group Members',
21 | type: 'array',
22 | items: {
23 | description: 'Member',
24 | type: 'object',
25 | additionalProperties: false,
26 | properties: {
27 | label: {
28 | type: 'string'
29 | },
30 | id: {
31 | type: 'string'
32 | }
33 | }
34 | }
35 | }
36 | },
37 | required: ['name', 'description']
38 | }
39 |
40 | export default group
41 |
--------------------------------------------------------------------------------
/static/src/js/schemas/groupSearch.js:
--------------------------------------------------------------------------------
1 | const groupSearch = {
2 | $schema: 'http://json-schema.org/draft-07/schema#',
3 | title: 'Group Search',
4 | type: 'object',
5 | additionalProperties: false,
6 | properties: {
7 | name: {
8 | description: 'The name of the group.',
9 | type: 'string'
10 | },
11 | providers: {
12 | description: 'The provider of the group.',
13 | type: 'array',
14 | items: {
15 | type: 'string',
16 | enum: ['MMT_1', 'MMT_2'] // Overwritten by GroupSearchForm.jsx
17 | }
18 | },
19 | members: {
20 | description: 'Group Members',
21 | type: 'array',
22 | items: {
23 | description: 'Member',
24 | type: 'object',
25 | additionalProperties: false,
26 | properties: {
27 | label: {
28 | type: 'string'
29 | },
30 | id: {
31 | type: 'string'
32 | }
33 | }
34 | }
35 | }
36 | },
37 | required: ['providers']
38 | }
39 |
40 | export default groupSearch
41 |
--------------------------------------------------------------------------------
/static/src/js/schemas/kms/urlTypeTool.js:
--------------------------------------------------------------------------------
1 | const urlTypes = [
2 | ['DistributionURL', 'DOWNLOAD SOFTWARE', 'MOBILE APP'],
3 | ['DistributionURL', 'DOWNLOAD SOFTWARE', ''],
4 | ['DistributionURL', 'GOTO WEB TOOL', 'LIVE ACCESS SERVER (LAS)'],
5 | ['DistributionURL', 'GOTO WEB TOOL', 'MAP VIEWER'],
6 | ['DistributionURL', 'GOTO WEB TOOL', 'SIMPLE SUBSET WIZARD (SSW)'],
7 | ['DistributionURL', 'GOTO WEB TOOL', 'SUBSETTER'],
8 | ['DistributionURL', 'GOTO WEB TOOL', '']
9 | ]
10 |
11 | export default urlTypes
12 |
--------------------------------------------------------------------------------
/static/src/js/schemas/orderOption.js:
--------------------------------------------------------------------------------
1 | const orderOption = {
2 | $schema: 'http://json-schema.org/draft-07/schema#',
3 | title: 'order-option',
4 | type: 'object',
5 | additionalProperties: false,
6 | properties: {
7 | name: {
8 | description: 'The name of the order option.',
9 | type: 'string',
10 | minLength: 1,
11 | maxLength: 85
12 | },
13 | sortKey: {
14 | description: 'The sort key of the order option.',
15 | type: 'string',
16 | minLength: 1,
17 | maxLength: 5
18 | },
19 | description: {
20 | description: 'The description of the order option.',
21 | type: 'string',
22 | minLength: 1,
23 | maxLength: 1024
24 | },
25 | deprecated: {
26 | description: 'Deprecated flag of the order option.',
27 | type: 'boolean',
28 | default: false
29 | },
30 | form: {
31 | description: 'The XML of the order option.',
32 | type: 'string'
33 | }
34 | },
35 | required: ['name', 'description', 'form']
36 | }
37 | export default orderOption
38 |
--------------------------------------------------------------------------------
/static/src/js/schemas/systemGroup.js:
--------------------------------------------------------------------------------
1 | const systemGroup = {
2 | $schema: 'http://json-schema.org/draft-07/schema#',
3 | title: 'group',
4 | type: 'object',
5 | additionalProperties: false,
6 | properties: {
7 | name: {
8 | description: 'The name of the group.',
9 | type: 'string',
10 | minLength: 1,
11 | maxLength: 85
12 | },
13 | description: {
14 | description: 'The description of the group.',
15 | type: 'string',
16 | minLength: 1,
17 | maxLength: 255
18 | },
19 | members: {
20 | description: 'Group Members',
21 | type: 'array',
22 | items: {
23 | description: 'Member',
24 | type: 'object',
25 | additionalProperties: false,
26 | properties: {
27 | label: {
28 | type: 'string'
29 | },
30 | id: {
31 | type: 'string'
32 | }
33 | }
34 | }
35 | }
36 | },
37 | required: ['name', 'description']
38 | }
39 |
40 | export default systemGroup
41 |
--------------------------------------------------------------------------------
/static/src/js/schemas/uiForms/citationConfiguration.js:
--------------------------------------------------------------------------------
1 | const citationConfiguration = [
2 | {
3 | displayName: 'Citation Information',
4 | properties: [
5 | 'Identifier',
6 | 'IdentifierType',
7 | 'Name',
8 | 'Abstract',
9 | 'ResolutionAuthority'
10 | ]
11 | },
12 | {
13 | displayName: 'Science Keywords',
14 | properties: ['ScienceKeywords']
15 | },
16 | {
17 | displayName: 'Related Identifiers',
18 | properties: [
19 | 'RelatedIdentifiers'
20 | ]
21 | },
22 | {
23 | displayName: 'Citation Metadata',
24 | properties: [
25 | 'CitationMetadata'
26 | ]
27 | }
28 | ]
29 |
30 | export default citationConfiguration
31 |
--------------------------------------------------------------------------------
/static/src/js/schemas/uiForms/index.js:
--------------------------------------------------------------------------------
1 | import citationConfiguration from './citationConfiguration'
2 | import collectionsConfiguration from './collectionsConfiguration'
3 | import servicesConfiguration from './serviceConfiguration'
4 | import toolsConfiguration from './toolsConfiguration'
5 | import variableConfiguration from './variableConfiguration'
6 | import visualizationConfiguration from './visualizationConfiguration'
7 |
8 | const formConfigurations = {
9 | Citation: citationConfiguration,
10 | Collection: collectionsConfiguration,
11 | Service: servicesConfiguration,
12 | Tool: toolsConfiguration,
13 | Variable: variableConfiguration,
14 | Visualization: visualizationConfiguration
15 | }
16 |
17 | export default formConfigurations
18 |
--------------------------------------------------------------------------------
/static/src/js/schemas/uiForms/toolsConfiguration.js:
--------------------------------------------------------------------------------
1 | const toolsConfiguration = [
2 | {
3 | displayName: 'Tool Information',
4 | properties: [
5 | 'Name',
6 | 'LongName',
7 | 'Version',
8 | 'VersionDescription',
9 | 'Type',
10 | 'LastUpdatedDate',
11 | 'Description',
12 | 'DOI',
13 | 'URL'
14 | ]
15 | },
16 | {
17 | displayName: 'Related URLs',
18 | properties: ['RelatedURLs']
19 | },
20 | {
21 | displayName: 'Compatibility And Usability',
22 | properties: [
23 | 'SupportedInputFormats',
24 | 'SupportedOutputFormats',
25 | 'SupportedOperatingSystems',
26 | 'SupportedBrowsers',
27 | 'SupportedSoftwareLanguages',
28 | 'Quality',
29 | 'AccessConstraints',
30 | 'UseConstraints'
31 | ]
32 | },
33 | {
34 | displayName: 'Descriptive Keywords',
35 | properties: ['ToolKeywords', 'AncillaryKeywords']
36 | },
37 | {
38 | displayName: 'Tool Organizations',
39 | properties: ['Organizations']
40 | },
41 | {
42 | displayName: 'Tool Contacts',
43 | properties: [
44 | 'ContactGroups',
45 | 'ContactPersons']
46 | },
47 | {
48 | displayName: 'Potential Action',
49 | properties: ['PotentialAction']
50 | }
51 | ]
52 |
53 | export default toolsConfiguration
54 |
--------------------------------------------------------------------------------
/static/src/js/schemas/uiForms/variableConfiguration.js:
--------------------------------------------------------------------------------
1 | const variableConfiguration = [
2 | {
3 | displayName: 'Variable Information',
4 | properties: [
5 | 'Name',
6 | 'StandardName',
7 | 'LongName',
8 | 'Definition',
9 | 'AdditionalIdentifiers',
10 | 'VariableType',
11 | 'VariableSubType',
12 | 'Units',
13 | 'DataType',
14 | 'Scale',
15 | 'Offset',
16 | 'ValidRanges',
17 | 'IndexRanges'
18 | ]
19 | },
20 | {
21 | displayName: 'Fill Values',
22 | properties: ['FillValues']
23 | },
24 | {
25 | displayName: 'Dimensions',
26 | properties: ['Dimensions']
27 | },
28 | {
29 | displayName: 'Measurement Identifiers',
30 | properties: ['MeasurementIdentifiers']
31 | },
32 | {
33 | displayName: 'Sampling Identifiers',
34 | properties: ['SamplingIdentifiers']
35 | },
36 | {
37 | displayName: 'Science Keywords',
38 | properties: ['ScienceKeywords']
39 | },
40 | {
41 | displayName: 'Sets',
42 | properties: ['Sets']
43 | },
44 | {
45 | displayName: 'Related URLs',
46 | properties: ['RelatedURLs']
47 | },
48 | {
49 | displayName: 'Instance Information',
50 | properties: ['InstanceInformation']
51 | }
52 | ]
53 | export default variableConfiguration
54 |
--------------------------------------------------------------------------------
/static/src/js/schemas/uiForms/visualizationConfiguration.js:
--------------------------------------------------------------------------------
1 | const visualizationConfiguration = [
2 | {
3 | displayName: 'Visualization Information',
4 | properties: [
5 | 'Identifier',
6 | 'Name',
7 | 'Title',
8 | 'Subtitle',
9 | 'Description',
10 | 'VisualizationType'
11 | ]
12 | },
13 | {
14 | displayName: 'Science Keywords',
15 | properties: ['ScienceKeywords']
16 | },
17 | {
18 | displayName: 'Spatial Extent',
19 | properties: [
20 | 'SpatialExtent'
21 | ]
22 | },
23 | {
24 | displayName: 'Temporal Extents',
25 | properties: [
26 | 'TemporalExtents'
27 | ]
28 | },
29 | {
30 | displayName: 'Specification',
31 | properties: [
32 | 'Specification'
33 | ]
34 | },
35 | {
36 | displayName: 'Generation',
37 | properties: [
38 | 'Generation'
39 | ]
40 | }
41 | ]
42 |
43 | export default visualizationConfiguration
44 |
--------------------------------------------------------------------------------
/static/src/js/schemas/uiSchemas/CollectionAssociation.js:
--------------------------------------------------------------------------------
1 | import CustomSelectWidget from '@/js/components/CustomSelectWidget/CustomSelectWidget'
2 |
3 | const collectionAssociationUiSchema = {
4 | 'ui:submitButtonOptions': {
5 | norender: true
6 | },
7 | 'ui:field': 'layout',
8 | 'ui:layout_grid': {
9 | 'ui:row': [
10 | {
11 | 'ui:col': {
12 | md: 12,
13 | children: [
14 | {
15 | 'ui:row': [
16 | {
17 | 'ui:col': {
18 | md: 12,
19 | children: ['SearchField']
20 | }
21 | }
22 | ]
23 | }
24 | ]
25 | }
26 | }
27 | ]
28 | },
29 | SearchField: {
30 | 'ui:required': true
31 | },
32 | ServiceField: {
33 | 'ui:options': {
34 | enumOptions: ['Service 1', 'Service 2'] // Overwritten by CollectionAssociationForm.jsx
35 | },
36 | 'ui:required': true,
37 | 'ui:title': 'Service',
38 | 'ui:widget': CustomSelectWidget
39 | }
40 | }
41 |
42 | export default collectionAssociationUiSchema
43 |
--------------------------------------------------------------------------------
/static/src/js/schemas/uiSchemas/SystemGroupSearch.js:
--------------------------------------------------------------------------------
1 | import CustomAsyncMultiSelectWidget from '@/js/components/CustomAsyncMultiSelectWidget/CustomAsyncMultiSelectWidget'
2 |
3 | import { getApplicationConfig } from '../../../../../sharedUtils/getConfig'
4 |
5 | const { apiHost } = getApplicationConfig()
6 |
7 | const systemGroupSearchUiSchema = {
8 | 'ui:submitButtonOptions': {
9 | norender: true
10 | },
11 | 'ui:field': 'layout',
12 | 'ui:layout_grid': {
13 | 'ui:row': [
14 | {
15 | 'ui:col': {
16 | md: 12,
17 | children: [
18 | {
19 | 'ui:row': [
20 | {
21 | 'ui:col': {
22 | md: 6,
23 | children: ['name']
24 | }
25 | },
26 | {
27 | 'ui:col': {
28 | md: 6,
29 | children: ['members']
30 | }
31 | }
32 | ]
33 | }
34 | ]
35 | }
36 | }
37 | ]
38 | },
39 | members: {
40 | 'ui:widget': CustomAsyncMultiSelectWidget,
41 | 'ui:search': {
42 | host: apiHost,
43 | endpoint: '/users',
44 | parameter: 'query'
45 | }
46 | }
47 | }
48 |
49 | export default systemGroupSearchUiSchema
50 |
--------------------------------------------------------------------------------
/static/src/js/schemas/uiSchemas/citations/citationScienceKeywordsUiSchema.js:
--------------------------------------------------------------------------------
1 | const citationScienceKeywordsUiSchema = {
2 | 'ui:heading-level': 'h3',
3 | 'ui:field': 'layout',
4 | 'ui:layout_grid': {
5 | 'ui:row': [
6 | {
7 | 'ui:group': 'Science Keywords',
8 | 'ui:required': true,
9 | 'ui:col': {
10 | md: 12,
11 | children: [
12 | {
13 | 'ui:row': [
14 | {
15 | 'ui:col': {
16 | md: 12,
17 | children: ['ScienceKeywords']
18 | }
19 | }
20 | ]
21 | }
22 | ]
23 | }
24 | }
25 | ]
26 | },
27 | ScienceKeywords: {
28 | 'ui:field': 'keywordPicker',
29 | 'ui:keyword_scheme': 'science_keywords',
30 | 'ui:picker_title': 'SERVICE KEYWORD',
31 | 'ui:keyword_scheme_column_names': ['sciencekeywords', 'category', 'topic', 'term', 'variable_level_1', 'variable_level_2', 'variable_level_3'],
32 | 'ui:filter': 'EARTH SCIENCE',
33 | 'ui:scheme_values': ['Category', 'Topic', 'Term', 'VariableLevel1', 'VariableLevel2', 'VariableLevel3']
34 | }
35 | }
36 |
37 | export default citationScienceKeywordsUiSchema
38 |
--------------------------------------------------------------------------------
/static/src/js/schemas/uiSchemas/citations/index.js:
--------------------------------------------------------------------------------
1 | import citationMetadataUiSchema from '@/js/schemas/uiSchemas/citations/citationMetadata'
2 | import relatedIdentifiersUiSchema from '@/js/schemas/uiSchemas/citations/relatedIdentifiers'
3 | import citationInformationUiSchema from './citationInformation'
4 | import citationScienceKeywordsUiSchema from './citationScienceKeywordsUiSchema'
5 |
6 | const citationUiSchema = {
7 | 'citation-information': citationInformationUiSchema,
8 | 'citation-metadata': citationMetadataUiSchema,
9 | 'related-identifiers': relatedIdentifiersUiSchema,
10 | 'science-keywords': citationScienceKeywordsUiSchema
11 | }
12 |
13 | export default citationUiSchema
14 |
--------------------------------------------------------------------------------
/static/src/js/schemas/uiSchemas/citations/relatedIdentifiers.js:
--------------------------------------------------------------------------------
1 | const relatedIdentifiersUiSchema = {
2 | 'ui:heading-level': 'h3',
3 | 'ui:field': 'layout',
4 | 'ui:layout_grid': {
5 | 'ui:row': [
6 | {
7 | 'ui:group': 'Related Identifiers',
8 | 'ui:required': false,
9 | 'ui:col': {
10 | md: 12,
11 | children: [
12 | {
13 | 'ui:row': [
14 | {
15 | 'ui:col': {
16 | md: 12,
17 | children: ['RelatedIdentifiers']
18 | }
19 | }
20 | ]
21 | }
22 | ]
23 | }
24 | }
25 | ]
26 | },
27 | RelatedIdentifiers: {
28 | items: {
29 | RelatedResolutionAuthority: {
30 | 'ui:widget': 'TextWidget'
31 | }
32 | }
33 | }
34 | }
35 |
36 | export default relatedIdentifiersUiSchema
37 |
--------------------------------------------------------------------------------
/static/src/js/schemas/uiSchemas/services/index.js:
--------------------------------------------------------------------------------
1 | import descriptiveKeywordsUiSchema from './descriptiveKeywords'
2 | import operationMetadataUiSchema from './operationMetadata'
3 | import relatedUrlsUiSchema from './related_urls'
4 | import serviceConstraintsUiSchema from './serviceConstraints'
5 | import serviceContactsUiSchema from './serviceContacts'
6 | import serviceInformationUiSchema from './serviceInformation'
7 | import organizationUiSchema from './serviceOrganizations'
8 | import serviceQualityUiSchema from './serviceQuality'
9 | import serviceOptionsUiSchema from './serviceOptions'
10 |
11 | const serviceUiSchema = {
12 | 'service-information': serviceInformationUiSchema,
13 | 'service-constraints': serviceConstraintsUiSchema,
14 | 'descriptive-keywords': descriptiveKeywordsUiSchema,
15 | 'service-organizations': organizationUiSchema,
16 | 'service-contacts': serviceContactsUiSchema,
17 | options: serviceOptionsUiSchema,
18 | 'operation-metadata': operationMetadataUiSchema,
19 | 'related-urls': relatedUrlsUiSchema,
20 | 'service-quality': serviceQualityUiSchema
21 | }
22 |
23 | export default serviceUiSchema
24 |
--------------------------------------------------------------------------------
/static/src/js/schemas/uiSchemas/services/serviceConstraints.js:
--------------------------------------------------------------------------------
1 | const serviceConstraintsUiSchema = {
2 | 'ui:submitButtonOptions': {
3 | norender: true
4 | },
5 | 'ui:heading-level': 'h3',
6 | 'ui:field': 'layout',
7 | 'ui:layout_grid': {
8 | 'ui:row': [
9 | {
10 | 'ui:group': 'Service Constraints',
11 | 'ui:col': {
12 | md: 12,
13 | children: [
14 | {
15 | 'ui:row': [
16 | {
17 | 'ui:col': {
18 | md: 12,
19 | children: ['AccessConstraints']
20 | }
21 | },
22 | {
23 | 'ui:col': {
24 | md: 12,
25 | children: ['UseConstraints']
26 | }
27 | }
28 | ]
29 | }
30 | ]
31 | }
32 | }
33 | ]
34 | },
35 | UseConstraints: {
36 | 'ui:heading-level': 'h4',
37 | LicenseText: {
38 | 'ui:widget': 'textarea'
39 | }
40 |
41 | },
42 | AccessConstraints: {
43 | 'ui:heading-level': 'h3',
44 | 'ui:widget': 'textarea'
45 | }
46 | }
47 | export default serviceConstraintsUiSchema
48 |
--------------------------------------------------------------------------------
/static/src/js/schemas/uiSchemas/tools/index.js:
--------------------------------------------------------------------------------
1 | import toolInformationUiSchema from './toolInformation'
2 | import relatedUrlUiSchema from './relatedUrls'
3 | import potentialActionUiSchema from './potentialAction'
4 | import compatabilityAndUsabilityUiSchema from './compatibilityAndUsability'
5 | import descriptiveKeywordsUiSchema from './descriptiveKeywords'
6 | import toolContactsUiSchema from './toolContacts'
7 | import organizationUiSchema from './organization'
8 |
9 | const toolsUiSchema = {
10 | 'tool-information': toolInformationUiSchema,
11 | 'related-urls': relatedUrlUiSchema,
12 | 'compatibility-and-usability': compatabilityAndUsabilityUiSchema,
13 | 'descriptive-keywords': descriptiveKeywordsUiSchema,
14 | 'tool-organizations': organizationUiSchema,
15 | 'tool-contacts': toolContactsUiSchema,
16 | 'potential-action': potentialActionUiSchema
17 | }
18 |
19 | export default toolsUiSchema
20 |
--------------------------------------------------------------------------------
/static/src/js/schemas/uiSchemas/variables/index.js:
--------------------------------------------------------------------------------
1 | import dimensionsUiSchema from './dimensions'
2 | import fillValuesUiSchema from './fillValue'
3 | import measurementIdentifiersUiSchema from './measurementIdentifiers'
4 | import SamplingIdentifiersUiSchema from './samplingIdentifiers'
5 | import scienceKeywordsUiSchema from './scienceKeywords'
6 | import variableInformationUiSchema from './variableInformation'
7 | import relatedUrlsUiSchema from './relatedUrls'
8 | import SetsUiSchema from './sets'
9 | import instanceInformationUiSchema from './instanceInformation'
10 |
11 | const variableUiSchema = {
12 | 'variable-information': variableInformationUiSchema,
13 | 'fill-values': fillValuesUiSchema,
14 | dimensions: dimensionsUiSchema,
15 | 'measurement-identifiers': measurementIdentifiersUiSchema,
16 | 'sampling-identifiers': SamplingIdentifiersUiSchema,
17 | 'science-keywords': scienceKeywordsUiSchema,
18 | sets: SetsUiSchema,
19 | 'related-urls': relatedUrlsUiSchema,
20 | 'instance-information': instanceInformationUiSchema
21 | }
22 |
23 | export default variableUiSchema
24 |
--------------------------------------------------------------------------------
/static/src/js/schemas/uiSchemas/variables/scienceKeywords.js:
--------------------------------------------------------------------------------
1 | const scienceKeywordsUiSchema = {
2 | 'ui:field': 'layout',
3 | 'ui:heading-level': 'h3',
4 | 'ui:layout_grid': {
5 | 'ui:row': [
6 | {
7 | 'ui:group': 'Science Keywords',
8 | 'ui:col': {
9 | md: 12,
10 | children: [
11 | {
12 | 'ui:row': [
13 | {
14 | 'ui:col': {
15 | md: 12,
16 | children: ['ScienceKeywords']
17 | }
18 | }
19 | ]
20 | }
21 | ]
22 | }
23 | }
24 | ]
25 | },
26 | ScienceKeywords: {
27 | 'ui:field': 'keywordPicker',
28 | 'ui:keyword_scheme': 'science_keywords',
29 | 'ui:picker_title': 'SERVICE KEYWORD',
30 | 'ui:keyword_scheme_column_names': ['sciencekeywords', 'category', 'topic', 'term', 'variable_level_1', 'variable_level_2', 'variable_level_3'],
31 | 'ui:filter': 'EARTH SCIENCE',
32 | 'ui:scheme_values': ['Category', 'Topic', 'Term', 'VariableLevel1', 'VariableLevel2', 'VariableLevel3']
33 | }
34 | }
35 | export default scienceKeywordsUiSchema
36 |
--------------------------------------------------------------------------------
/static/src/js/schemas/uiSchemas/visualizations/index.js:
--------------------------------------------------------------------------------
1 | import generationUiSchema from './generation'
2 | import scienceKeywordsUiSchema from './scienceKeywords'
3 | import spatialExtentUiSchema from './spatialExtent'
4 | import specificationUiSchema from './specification'
5 | import temporalExtentsUiSchema from './temporalExtents'
6 | import visualizationInformationUiSchema from './visualizationInformation'
7 |
8 | const visualizationUiSchema = {
9 | 'visualization-information': visualizationInformationUiSchema,
10 | 'science-keywords': scienceKeywordsUiSchema,
11 | 'spatial-extent': spatialExtentUiSchema,
12 | 'temporal-extents': temporalExtentsUiSchema,
13 | generation: generationUiSchema,
14 | specification: specificationUiSchema
15 | }
16 |
17 | export default visualizationUiSchema
18 |
--------------------------------------------------------------------------------
/static/src/js/schemas/uiSchemas/visualizations/scienceKeywords.js:
--------------------------------------------------------------------------------
1 | const scienceKeywordsUiSchema = {
2 | 'ui:heading-level': 'h3',
3 | 'ui:field': 'layout',
4 | 'ui:layout_grid': {
5 | 'ui:row': [
6 | {
7 | 'ui:group': 'Science Keywords',
8 | 'ui:required': true,
9 | 'ui:col': {
10 | md: 12,
11 | children: [
12 | {
13 | 'ui:row': [
14 | {
15 | 'ui:col': {
16 | md: 12,
17 | children: ['ScienceKeywords']
18 | }
19 | }
20 | ]
21 | }
22 | ]
23 | }
24 | }
25 | ]
26 | },
27 | ScienceKeywords: {
28 | 'ui:field': 'keywordPicker',
29 | 'ui:keyword_scheme': 'science_keywords',
30 | 'ui:picker_title': 'SERVICE KEYWORD',
31 | 'ui:keyword_scheme_column_names': ['sciencekeywords', 'category', 'topic', 'term', 'variable_level_1', 'variable_level_2', 'variable_level_3'],
32 | 'ui:filter': 'EARTH SCIENCE',
33 | 'ui:scheme_values': ['Category', 'Topic', 'Term', 'VariableLevel1', 'VariableLevel2', 'VariableLevel3']
34 | }
35 | }
36 |
37 | export default scienceKeywordsUiSchema
38 |
--------------------------------------------------------------------------------
/static/src/js/schemas/umm/ummCTemplateSchema.js:
--------------------------------------------------------------------------------
1 | import ummCSchema from './ummCSchema'
2 |
3 | const ummCTemplateSchema = {
4 | $schema: 'http://json-schema.org/draft-07/schema#',
5 | type: 'object',
6 | additionalProperties: false,
7 | properties: {
8 | TemplateName: {
9 | description: 'The language used in the metadata record.',
10 | $ref: '#/definitions/TemplateType'
11 | },
12 | ...ummCSchema.properties
13 | },
14 | required: ['TemplateName', ...ummCSchema.required],
15 | definitions: {
16 | TemplateType: {
17 | description: 'The name associated with the collection template',
18 | type: 'string',
19 | minLength: 1,
20 | maxLength: 85
21 | },
22 | ...ummCSchema.definitions
23 | }
24 | }
25 |
26 | export default ummCTemplateSchema
27 |
--------------------------------------------------------------------------------
/static/src/js/utils/__tests__/checkForCMRFetchDraftLag.test.js:
--------------------------------------------------------------------------------
1 | import checkForCMRFetchDraftLag from '../checkForCMRFetchDraftLag'
2 |
3 | describe('checkForCMRFetchDraftLag', () => {
4 | describe('when the fetched revision Id and expected revision Id are undefined', () => {
5 | test('returns undefined', () => {
6 | expect(checkForCMRFetchDraftLag(`${undefined}`, `${undefined}`)).toEqual(undefined)
7 | })
8 | })
9 |
10 | describe('when the fetched revision Id is less than the expected revision Id, indicating a cmr lag', () => {
11 | test('throws error', () => {
12 | expect(() => checkForCMRFetchDraftLag('1', '2')).toThrow('Delay in CMR has been detected. Refresh the page in order to see latest revision')
13 | })
14 | })
15 |
16 | describe('when the fetched revision Id is greater than the expected revision Id, indicating a draft has been published', () => {
17 | test('throws error', () => {
18 | expect(() => checkForCMRFetchDraftLag('2', '1')).not.toThrow('Delay in CMR has been detected. Refresh the page in order to see latest revision')
19 | })
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/static/src/js/utils/__tests__/constructDownloadableFile.test.js:
--------------------------------------------------------------------------------
1 | import constructDownloadableFile from '../constructDownloadableFile'
2 |
3 | describe('constructDownloadableFile', () => {
4 | test('downloads a json file', () => {
5 | HTMLAnchorElement.prototype.click = vi.fn()
6 | global.URL.createObjectURL = vi.fn().mockReturnValue('mock-url')
7 |
8 | const mockBlob = {
9 | size: 155,
10 | type: 'application/json;charset:utf-8'
11 | }
12 | const blobSpy = vi.spyOn(global, 'Blob').mockImplementationOnce(() => mockBlob)
13 |
14 | const contents = JSON.stringify({
15 | mock: 'data'
16 | }, null, 2)
17 | const name = 'mock-file'
18 |
19 | constructDownloadableFile(contents, name)
20 |
21 | expect(blobSpy).toHaveBeenCalledTimes(1)
22 | expect(blobSpy).toHaveBeenCalledWith([contents], { type: 'application/json;charset:utf-8' })
23 |
24 | expect(global.URL.createObjectURL).toHaveBeenCalledTimes(1)
25 | expect(global.URL.createObjectURL).toHaveBeenCalledWith(mockBlob)
26 |
27 | const link = document.getElementsByTagName('a')[0]
28 | expect(link.getAttribute('download')).toEqual('mock-file')
29 | expect(link.getAttribute('href')).toEqual('mock-url')
30 |
31 | expect(HTMLAnchorElement.prototype.click).toHaveBeenCalledTimes(1)
32 | })
33 | })
34 |
--------------------------------------------------------------------------------
/static/src/js/utils/__tests__/convertToDottedNotation.test.js:
--------------------------------------------------------------------------------
1 | import convertToDottedNotation from '../convertToDottedNotation'
2 |
3 | describe('convertToDottedNotation', () => {
4 | describe('when the property has underscores', () => {
5 | test('returns the path', () => {
6 | expect(convertToDottedNotation('SamplePath_1_a')).toEqual('SamplePath.1.a')
7 | })
8 | })
9 |
10 | describe('when the property starts with underscore', () => {
11 | test('returns the path', () => {
12 | expect(convertToDottedNotation('_SamplePath_1')).toEqual('.SamplePath.1')
13 | })
14 | })
15 |
16 | describe('when the property has no underscore', () => {
17 | test('returns the path', () => {
18 | expect(convertToDottedNotation('SamplePath')).toEqual('SamplePath')
19 | })
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/static/src/js/utils/__tests__/createPath.test.js:
--------------------------------------------------------------------------------
1 | import createPath from '../createPath'
2 |
3 | describe('createPath', () => {
4 | describe('when the property is a top level field', () => {
5 | test('returns the path', () => {
6 | expect(createPath('SamplePath')).toEqual('SamplePath')
7 | })
8 | })
9 |
10 | describe('when the property is an array field', () => {
11 | test('returns the path', () => {
12 | expect(createPath('SamplePath.1')).toEqual('SamplePath[1]')
13 | })
14 | })
15 |
16 | describe('when the property is nested field under an array field', () => {
17 | test('returns the path', () => {
18 | expect(createPath('SamplePath.1.ArrayThing')).toEqual('SamplePath[1].ArrayThing')
19 | })
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/static/src/js/utils/__tests__/createTemplate.test.js:
--------------------------------------------------------------------------------
1 | import createTemplate from '../createTemplate'
2 |
3 | describe('createTemplates', () => {
4 | describe('when createTemplate response is ok', () => {
5 | test('return a success response with template id', async () => {
6 | global.fetch.mockResolvedValue({
7 | ok: true,
8 | json: () => Promise.resolve({
9 | id: 'New Template'
10 | })
11 | })
12 |
13 | const providerId = 'mock-provider-id'
14 | const token = 'mock-jwt'
15 | const ummMetadata = {
16 | mock: 'mock ummMetadata'
17 | }
18 |
19 | const response = await createTemplate(providerId, token, ummMetadata)
20 |
21 | expect(response).toEqual({ id: 'New Template' })
22 | expect(fetch).toHaveBeenCalledTimes(1)
23 | })
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/static/src/js/utils/__tests__/errorLogger.test.js:
--------------------------------------------------------------------------------
1 | import errorLogger from '../errorLogger'
2 |
3 | beforeEach(() => {
4 | vi.clearAllMocks()
5 | })
6 |
7 | global.fetch = vi.fn(() => Promise.resolve({
8 | text: () => Promise.resolve()
9 | }))
10 |
11 | describe('errorLogger', () => {
12 | describe('when errorLogger is call successfully', () => {
13 | test('a error message is logged', async () => {
14 | const error = {
15 | message: 'Mock error',
16 | stack: 'Mock stack'
17 | }
18 | const action = 'Error Logger test'
19 |
20 | await errorLogger(error, action)
21 |
22 | expect(fetch).toHaveBeenCalledTimes(1)
23 | })
24 | })
25 |
26 | describe('when errorLogger call fails', () => {
27 | test('return undefined response', async () => {
28 | fetch.mockImplementationOnce(() => Promise.reject(new Error('error')))
29 | const error = {
30 | message: 'Mock error',
31 | stack: 'Mock stack'
32 | }
33 | const action = 'Error Logger test'
34 |
35 | const response = await errorLogger(error, action)
36 | expect(response).toEqual(undefined)
37 | expect(fetch).toHaveBeenCalledTimes(1)
38 | })
39 | })
40 | })
41 |
--------------------------------------------------------------------------------
/static/src/js/utils/__tests__/getHumanizedNameFromTypeParam.test.js:
--------------------------------------------------------------------------------
1 | import getHumanizedNameFromTypeParam from '../getHumanizedNameFromTypeParam'
2 |
3 | describe('getHumanizedNameFromTypeParam', () => {
4 | describe('when provided type and true for plural arguments', () => {
5 | test('returns humanized and pluralized string', () => {
6 | expect(getHumanizedNameFromTypeParam('collections', true)).toEqual('collections')
7 | })
8 | })
9 |
10 | describe('when provided type and false for plural arguments', () => {
11 | test('returns humanized and pluralized string', () => {
12 | expect(getHumanizedNameFromTypeParam('collections', false)).toEqual('collection')
13 | })
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/static/src/js/utils/__tests__/getKeywords.test.js:
--------------------------------------------------------------------------------
1 | import getKeywords from '../getKeywords'
2 | import relatedUrls from '../__mocks__/relatedUrls'
3 |
4 | describe('getKeywords', () => {
5 | describe('Traverses a CMR facet response and returns a list of keywords for the specified type', () => {
6 | test('returns the keywords under the specified type', () => {
7 | const keywords = getKeywords(relatedUrls, 'type', { url_content_type: 'DistributionURL' }, ['url_content_type', 'type'])
8 | expect(keywords).toEqual([
9 | 'GET CAPABILITIES',
10 | 'GET DATA VIA DIRECT ACCESS',
11 | 'USE SERVICE API',
12 | 'GET DATA',
13 | 'DOWNLOAD SOFTWARE', 'GOTO WEB TOOL'])
14 | })
15 | })
16 |
17 | describe('Traverses a CMR facet response and returns [] no keywords if the specified type does not exist', () => {
18 | test('returns no keywords', () => {
19 | const keywords = getKeywords(relatedUrls, 'typo', { url_content_type: 'DistributionURL' }, ['url_content_type', 'subtype'])
20 | expect(keywords).toEqual([])
21 | })
22 | })
23 | })
24 |
--------------------------------------------------------------------------------
/static/src/js/utils/__tests__/getTagCount.test.js:
--------------------------------------------------------------------------------
1 | import getTagCount from '../getTagCount'
2 |
3 | describe('getTagCount', () => {
4 | describe('when the formData had two items', () => {
5 | test('should return 2', () => {
6 | const inputObject = {
7 | items: [
8 | {
9 | mockTestData: 'test1'
10 | },
11 | {
12 | mockTestData: 'test2'
13 |
14 | }
15 | ]
16 | }
17 | const result = getTagCount(inputObject)
18 | expect(result).toEqual(2)
19 | })
20 | })
21 |
22 | describe('when the formData had no items', () => {
23 | test('should return 0', () => {
24 | const inputObject = null
25 | const result = getTagCount(inputObject)
26 | expect(result).toEqual(0)
27 | })
28 | })
29 | })
30 |
--------------------------------------------------------------------------------
/static/src/js/utils/__tests__/getVersionName.test.js:
--------------------------------------------------------------------------------
1 | import {
2 | describe,
3 | test,
4 | expect
5 | } from 'vitest'
6 | import { getVersionName } from '../getVersionName'
7 |
8 | describe('getVersionName', () => {
9 | test('When version_type is published, should return "published"', () => {
10 | const version = {
11 | version: '1.0.0',
12 | version_type: 'published'
13 | }
14 | expect(getVersionName(version)).toBe('published')
15 | })
16 |
17 | test('When version_type is not published, should return the version string', () => {
18 | const version = {
19 | version: '1.0.0',
20 | version_type: 'draft'
21 | }
22 | expect(getVersionName(version)).toBe('1.0.0')
23 | })
24 |
25 | test('When version_type is undefined, should return the version string', () => {
26 | const version = { version: '2.0.0' }
27 | expect(getVersionName(version)).toBe('2.0.0')
28 | })
29 |
30 | test('When version is null or undefined, should return null', () => {
31 | expect(getVersionName(null)).toBe(null)
32 | expect(getVersionName(undefined)).toBe(null)
33 | })
34 | })
35 |
--------------------------------------------------------------------------------
/static/src/js/utils/__tests__/isTokenExpired.test.js:
--------------------------------------------------------------------------------
1 | import isTokenExpired from '../isTokenExpired'
2 |
3 | describe('isTokenExpired', () => {
4 | describe('when the token is expired', () => {
5 | test('returns true', () => {
6 | expect(isTokenExpired(new Date().getTime() - 1)).toEqual(true)
7 | })
8 | })
9 |
10 | describe('when the token is not expired', () => {
11 | test('returns false', () => {
12 | expect(isTokenExpired(new Date().getTime() + 1)).toEqual(false)
13 | })
14 | })
15 |
16 | describe('when the token does not exist', () => {
17 | test('returns true', () => {
18 | expect(isTokenExpired()).toEqual(true)
19 | })
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/static/src/js/utils/__tests__/parseError.test.js:
--------------------------------------------------------------------------------
1 | import parseError from '../parseError'
2 |
3 | describe('parseError', () => {
4 | describe('when the error is a graphql error', () => {
5 | test('returns the error messages joined', () => {
6 | const error = {
7 | graphQLErrors: [{
8 | message: 'Mock GraphQL Error 1'
9 | }, {
10 | message: 'Mock GraphQL Error 2'
11 | }]
12 | }
13 |
14 | expect(parseError(error)).toEqual(`Mock GraphQL Error 1
15 | Mock GraphQL Error 2`)
16 | })
17 | })
18 |
19 | describe('when the error is a network error', () => {
20 | test('returns the path', () => {
21 | const error = {
22 | networkError: {
23 | message: 'Mock Network Error'
24 | }
25 | }
26 |
27 | expect(parseError(error)).toEqual('Mock Network Error')
28 | })
29 | })
30 | })
31 |
--------------------------------------------------------------------------------
/static/src/js/utils/__tests__/prefixProperty.test.js:
--------------------------------------------------------------------------------
1 | import prefixProperty from '../prefixProperty'
2 |
3 | describe('prefixProperty', () => {
4 | describe('when the property does not start with a `.`', () => {
5 | test('returns the prefixed property', () => {
6 | expect(prefixProperty('SampleProperty')).toEqual('.SampleProperty')
7 | })
8 | })
9 |
10 | describe('when the property does not start with a `.`', () => {
11 | test('returns the property', () => {
12 | expect(prefixProperty('.SampleProperty')).toEqual('.SampleProperty')
13 | })
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/static/src/js/utils/__tests__/removeMetadataKeys.test.js:
--------------------------------------------------------------------------------
1 | import removeMetadataKeys from '../removeMetadataKeys'
2 |
3 | describe('removeMetadataKeys', () => {
4 | test('should remove Name and LongName from the metadata', () => {
5 | const metadata = {
6 | Name: 'test name',
7 | LongName: 'test long name',
8 | Description: 'test description'
9 | }
10 | const keys = ['Name', 'LongName']
11 |
12 | const removeKeys = removeMetadataKeys(metadata, keys)
13 |
14 | expect(removeKeys).toMatchObject({ Description: 'test description' })
15 | })
16 |
17 | test('should remove ShortName and EntryTitle from the metadata', () => {
18 | const metadata = {
19 | ShortName: 'test short name',
20 | EntryTitle: 'test entry title',
21 | Description: 'test description'
22 | }
23 | const keys = ['ShortName', 'EntryTitle']
24 |
25 | const removeKeys = removeMetadataKeys(metadata, keys)
26 |
27 | expect(removeKeys).toMatchObject({ Description: 'test description' })
28 | })
29 | })
30 |
--------------------------------------------------------------------------------
/static/src/js/utils/__tests__/shouldHideGetData.test.js:
--------------------------------------------------------------------------------
1 | import shouldHideGetData from '../shouldHideGetData'
2 |
3 | describe('shouldHideGetData', () => {
4 | describe('when URLContentType is not DistributionURL and Type is not GET DATA or GET CAPABILITIES', () => {
5 | test('should hide GetData form', () => {
6 | const props = {
7 | URLContentType: 'OtherContentType',
8 | Type: 'OtherType'
9 | }
10 | const getData = shouldHideGetData(props)
11 | expect(getData).toBe(true)
12 | })
13 | })
14 |
15 | describe('when URLContentType is DistributionURL and Type is GET DATA', () => {
16 | test('should not hide GetData form', () => {
17 | const props = {
18 | URLContentType: 'DistributionURL',
19 | Type: 'GET DATA'
20 | }
21 |
22 | const getData = shouldHideGetData(props)
23 | expect(getData).toBe(false)
24 | })
25 | })
26 |
27 | describe('when URLContentType is DistributionURL and Type is GET CAPABILITIES', () => {
28 | test('should not hide GetData', () => {
29 | const props = {
30 | URLContentType: 'DistributionURL',
31 | Type: 'GET CAPABILITIES'
32 | }
33 | const getData = shouldHideGetData(props)
34 | expect(getData).toBe(false)
35 | })
36 | })
37 | })
38 |
--------------------------------------------------------------------------------
/static/src/js/utils/__tests__/shouldHideGetService.test.js:
--------------------------------------------------------------------------------
1 | import shouldHideGetService from '../shouldHideGetService'
2 |
3 | describe('shouldHideGetService', () => {
4 | describe('when URLContentType is not DistributionURL and Type is not USE SERVICE API', () => {
5 | test('should hide GetService form', () => {
6 | const props = {
7 | URLContentType: 'OtherContentType',
8 | Type: 'OtherType'
9 | }
10 | const getService = shouldHideGetService(props)
11 | expect(getService).toBe(true)
12 | })
13 | })
14 |
15 | describe('when URLContentType is DistributionURL and Type is USE SERVICE API', () => {
16 | test('should not hide GetService from ', () => {
17 | const props = {
18 | URLContentType: 'DistributionURL',
19 | Type: 'USE SERVICE API'
20 | }
21 | const getService = shouldHideGetService(props)
22 | expect(getService).toBe(false)
23 | })
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/static/src/js/utils/__tests__/toKebabCase.test.js:
--------------------------------------------------------------------------------
1 | import toKebabCase from '../toKebabCase'
2 |
3 | describe('toKebabCase', () => {
4 | describe('when the display name is "Related URLs"', () => {
5 | test('returns a lower cased kebab string ', () => {
6 | expect(toKebabCase('Related URLs')).toEqual('related-urls')
7 | })
8 | })
9 |
10 | describe('when the display name is "Tool Information"', () => {
11 | test('returns a lower cased kebab string ', () => {
12 | expect(toKebabCase('Tool Information')).toEqual('tool-information')
13 | })
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/static/src/js/utils/__tests__/toTitleCase.test.js:
--------------------------------------------------------------------------------
1 | import toTitleCase from '../toTitleCase'
2 |
3 | describe('toTitleCase', () => {
4 | test('converts "OrderOption" to "Order Option"', () => {
5 | expect(toTitleCase('OrderOption')).toEqual('Order Option')
6 | })
7 |
8 | test('converts "order-option" to "Order Option"', () => {
9 | expect(toTitleCase('order-option')).toEqual('Order Option')
10 | })
11 |
12 | test('converts "collection" to "Collection"', () => {
13 | expect(toTitleCase('collection')).toEqual('Collection')
14 | })
15 |
16 | test('converts "Collection" to "Collection"', () => {
17 | expect(toTitleCase('Collection')).toEqual('Collection')
18 | })
19 | })
20 |
--------------------------------------------------------------------------------
/static/src/js/utils/checkForCMRFetchDraftLag.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Compares the revision Ids to ensure the draft retched is the most current
3 | * @param {String} fetchedRevisionId The revision Id that comes back from CMR
4 | * @param {String} expectedRevisionId The revision Id that was set on ingest (expected revision)
5 | */
6 |
7 | const checkForCMRFetchDraftLag = (fetchedRevisionId, expectedRevisionId) => {
8 | if ((fetchedRevisionId && expectedRevisionId) && (fetchedRevisionId < expectedRevisionId)) {
9 | throw new Error('Delay in CMR has been detected. Refresh the page in order to see latest revision')
10 | }
11 | }
12 |
13 | export default checkForCMRFetchDraftLag
14 |
--------------------------------------------------------------------------------
/static/src/js/utils/constructDownloadableFile.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Construct and trigger the browser to download a string as a file
3 | * @param {String} contents The data to write to the file
4 | * @param {String} name What to name the file
5 | * @param {String} type The filetype of the file to create
6 | */
7 | export const constructDownloadableFile = (contents, name, type = 'application/json;charset:utf-8') => {
8 | const clickableElement = document.createElement('a')
9 | const fileObject = new Blob([contents], { type })
10 |
11 | clickableElement.href = URL.createObjectURL(fileObject)
12 | clickableElement.download = name
13 |
14 | // Required for file downloads to work in Firefox
15 | document.body.appendChild(clickableElement)
16 | clickableElement.click()
17 | }
18 |
19 | export default constructDownloadableFile
20 |
--------------------------------------------------------------------------------
/static/src/js/utils/convertToDottedNotation.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Given a string, this will return a . notation of the string
3 | * @param {String} property A string that needs to be converted.
4 | */
5 | const convertToDottedNotation = (property) => property.replace(/_/g, '.')
6 |
7 | export default convertToDottedNotation
8 |
--------------------------------------------------------------------------------
/static/src/js/utils/createPath.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Converts a JSON Schema error `property` into a path used to traverse the JSON object
3 | * @param {String} property JSON Schema error `property` field
4 | */
5 | const createPath = (property) => {
6 | let path = property
7 |
8 | path = path.replace(/\.(\d)/g, '[$1')
9 | path = path.replace(/(\d)\./g, '$1].')
10 |
11 | if (path.match(/^.*\d$/)) { // Ends with a digit
12 | path += ']'
13 | }
14 |
15 | return path
16 | }
17 |
18 | export default createPath
19 |
--------------------------------------------------------------------------------
/static/src/js/utils/createResponseFromKeywords.js:
--------------------------------------------------------------------------------
1 | import { cloneDeep } from 'lodash-es'
2 | import traverseArray from './traverseArray'
3 | import walkMap from './walkMap'
4 | /**
5 | * Given array of keyowrds, i.e.,
6 | * ['DistributionURL', 'DOWNLOAD SOFTWARE', 'MOBILE APP'],
7 | * ['DistributionURL', 'DOWNLOAD SOFTWARE'],
8 | * ['DistributionURL', 'GOTO WEB TOOL', 'LIVE ACCESS SERVER (LAS)'],
9 | * ['DistributionURL', 'GOTO WEB TOOL', 'SUBSETTER'],
10 | * ['DistributionURL', 'GOTO WEB TOOL']
11 | * with array of keys (i.e., ['url_content_type', 'type', 'subtype'])
12 | * it will return a cmr response object, i.e,
13 | * field: [
14 | * {subfields: ..., value:..., subfield1:...},
15 | * {subfields: ..., value:..., subfield1:...},
16 | * ]
17 | * @param {Array} keywords Array of keywords
18 | * @param {Array} keys Array of keys
19 | */
20 | const createResponseFromKeywords = (keywords, keys) => {
21 | const map = {}
22 | const paths = cloneDeep(keywords)
23 | paths.forEach((tokens) => {
24 | traverseArray(map, tokens)
25 | })
26 |
27 | const current = cloneDeep(keys.shift())
28 | const response = {}
29 |
30 | walkMap(map, current, keys, response)
31 |
32 | return response
33 | }
34 |
35 | export default createResponseFromKeywords
36 |
--------------------------------------------------------------------------------
/static/src/js/utils/createTemplate.js:
--------------------------------------------------------------------------------
1 | import { getApplicationConfig } from '../../../../sharedUtils/getConfig'
2 |
3 | /**
4 | * Calls /providers/{providerId}/template/{id} lambda to get a create a new template
5 | * @param {string} providerId A provider id that a given user is using
6 | * @param {Object} token A users token
7 | * @param {Object} ummMetadata An object with the metadata key value pairs
8 | */
9 | const createTemplate = async (providerId, token, ummMetadata) => {
10 | const { apiHost } = getApplicationConfig()
11 |
12 | const response = await fetch(`${apiHost}/providers/${providerId}/templates`, {
13 | method: 'POST',
14 | headers: {
15 | Authorization: `Bearer ${token}`
16 | },
17 | body: JSON.stringify({
18 | ...ummMetadata
19 | })
20 | })
21 | const data = await response.json()
22 |
23 | return data
24 | }
25 |
26 | export default createTemplate
27 |
--------------------------------------------------------------------------------
/static/src/js/utils/deleteTemplate.js:
--------------------------------------------------------------------------------
1 | import { getApplicationConfig } from '../../../../sharedUtils/getConfig'
2 |
3 | /**
4 | * Calls /providers/{providerId}/template/{id} lambda to get a delete a template
5 | * @param {string} providerId A provider id that a given user is using
6 | * @param {Object} token A users token
7 | * @param {Object} id An id of the template
8 | */
9 | const delateTemplate = async (providerId, token, id) => {
10 | const { apiHost } = getApplicationConfig()
11 |
12 | const response = await fetch(`${apiHost}/providers/${providerId}/templates/${id}`, {
13 | method: 'DELETE',
14 | headers: {
15 | Authorization: `Bearer ${token}`
16 | }
17 | })
18 | const data = await response
19 |
20 | if (response.ok) {
21 | return { response: data }
22 | }
23 |
24 | return { error: response }
25 | }
26 |
27 | export default delateTemplate
28 |
--------------------------------------------------------------------------------
/static/src/js/utils/errorLogger.js:
--------------------------------------------------------------------------------
1 | import { getApplicationConfig } from '../../../../sharedUtils/getConfig'
2 |
3 | /**
4 | * Calls errorLogger lambda /error-logger to log out any error
5 | * @param {Object} error An error object with the error message and stacktrace.
6 | * @param {string} action A string describing the action attempted.
7 | */
8 | const errorLogger = async (error, action) => {
9 | const location = window.location.href
10 | const errorObj = {
11 | message: error.message,
12 | stack: error.stack,
13 | location,
14 | action
15 | }
16 | const { apiHost } = getApplicationConfig()
17 |
18 | const options = {
19 | method: 'POST',
20 | body: JSON.stringify(errorObj)
21 | }
22 |
23 | await fetch(`${apiHost}/error-logger`, (options))
24 | .then((response) => response.text())
25 | .catch((err) => err)
26 | }
27 |
28 | export default errorLogger
29 |
--------------------------------------------------------------------------------
/static/src/js/utils/fetchCmrKeywords.js:
--------------------------------------------------------------------------------
1 | import errorLogger from './errorLogger'
2 | import { getApplicationConfig } from '../../../../sharedUtils/getConfig'
3 |
4 | /**
5 | * Calls cmr /keywords/ endpoint to get a list of keywords
6 | * @param {String} scheme keyword name
7 | */
8 | const fetchCmrKeywords = async (scheme) => {
9 | const { cmrHost } = getApplicationConfig()
10 |
11 | let res = null
12 | await fetch(`${cmrHost}/search/keywords/${scheme}`)
13 | .then((response) => {
14 | res = response.json()
15 | })
16 | .catch((errors) => {
17 | errorLogger(errors)
18 | })
19 |
20 | return res
21 | }
22 |
23 | export default fetchCmrKeywords
24 |
--------------------------------------------------------------------------------
/static/src/js/utils/getConceptTypeByConceptId.js:
--------------------------------------------------------------------------------
1 | import conceptIdTypes from '../constants/conceptIdTypes'
2 |
3 | /**
4 | * Find the concept type based on the provided conceptId
5 | * @param {String} conceptId concept ID to determine the concept type
6 | */
7 | const getConceptTypeByConceptId = (conceptId) => {
8 | if (!conceptId) return undefined
9 |
10 | if (conceptId.startsWith('CIT')) {
11 | return conceptIdTypes.CIT
12 | }
13 |
14 | if (conceptId.startsWith('VIS')) {
15 | return conceptIdTypes.VIS
16 | }
17 |
18 | const prefix = conceptId.charAt(0)
19 |
20 | return conceptIdTypes[prefix]
21 | }
22 |
23 | export default getConceptTypeByConceptId
24 |
--------------------------------------------------------------------------------
/static/src/js/utils/getConceptTypeByDraftConceptId.js:
--------------------------------------------------------------------------------
1 | import draftConceptIdTypes from '../constants/draftConceptIdTypes'
2 |
3 | /**
4 | * Find the concept type based on the provided draftConceptId
5 | * @param {String} draftConceptId Draft concept ID to determine the concept type
6 | */
7 | const getConceptTypeByDraftConceptId = (draftConceptId) => {
8 | if (!draftConceptId) return undefined
9 |
10 | if (draftConceptId.startsWith('VISD')) {
11 | return draftConceptIdTypes.VISD
12 | }
13 |
14 | if (draftConceptId.startsWith('CITD')) {
15 | return draftConceptIdTypes.CITD
16 | }
17 |
18 | const prefix = draftConceptId.substring(0, 2)
19 |
20 | return draftConceptIdTypes[prefix]
21 | }
22 |
23 | export default getConceptTypeByDraftConceptId
24 |
--------------------------------------------------------------------------------
/static/src/js/utils/getFormSchema.js:
--------------------------------------------------------------------------------
1 | import toKebabCase from './toKebabCase'
2 |
3 | /**
4 | * Gets the form schema based on the current section
5 | * @param {Object} fullSchema A full schema.
6 | * @param {Object[]} formConfigurations A configurations of the form with the list of field in each section.
7 | * @param {string} formName A form's title.
8 | */
9 | const getFormSchema = ({
10 | fullSchema,
11 | formConfigurations,
12 | formName
13 | }) => {
14 | const config = formConfigurations
15 | .find((formConfig) => toKebabCase(formConfig.displayName) === formName)
16 |
17 | const { properties } = config
18 |
19 | const schemaProperties = {}
20 | properties.forEach((property) => {
21 | schemaProperties[property] = fullSchema.properties[property]
22 | })
23 |
24 | const required = fullSchema.required.filter((field) => properties.includes(field))
25 |
26 | return {
27 | $id: fullSchema.$id,
28 | $schema: fullSchema.$schema,
29 | definitions: fullSchema.definitions,
30 | required,
31 | properties: schemaProperties
32 | }
33 | }
34 |
35 | export default getFormSchema
36 |
--------------------------------------------------------------------------------
/static/src/js/utils/getHumanizedNameFromTypeParam.js:
--------------------------------------------------------------------------------
1 | import typeParamToHumanizedStringMap from '../constants/typeParamToHumanizedStringMap'
2 |
3 | /**
4 | * Takes a type from the url and returns a humanized singular or plural version
5 | * @param {String} type The type from the url.
6 | * @param {Boolean} [plural] A boolean that determines whether or not the string should be plural
7 | */
8 |
9 | const getHumanizedNameFromTypeParam = (type, plural) => {
10 | const humanizedName = typeParamToHumanizedStringMap[type]
11 |
12 | return plural ? `${humanizedName}s` : humanizedName
13 | }
14 |
15 | export default getHumanizedNameFromTypeParam
16 |
--------------------------------------------------------------------------------
/static/src/js/utils/getKeywordRecommendations.js:
--------------------------------------------------------------------------------
1 | import { getApplicationConfig } from '../../../../sharedUtils/getConfig'
2 |
3 | /**
4 | * Calls gkrKeywordRecommendations lambda /gkr-request-keywords to fetch a list of recommended keywords
5 | * given a metadata summary
6 | * @param {Object} summary The metadata summary or abstract
7 | */
8 | const getKeywordRecommendations = async (summary) => {
9 | const query = {
10 | description: summary
11 | }
12 |
13 | const { apiHost } = getApplicationConfig()
14 |
15 | const options = {
16 | method: 'POST',
17 | body: JSON.stringify(query)
18 | }
19 |
20 | return fetch(`${apiHost}/gkr-keyword-recommendations`, (options))
21 | .then((response) => response.json())
22 | .catch((err) => err)
23 | }
24 |
25 | export default getKeywordRecommendations
26 |
--------------------------------------------------------------------------------
/static/src/js/utils/getNextFormName.js:
--------------------------------------------------------------------------------
1 | import toKebabCase from './toKebabCase'
2 |
3 | /**
4 | * Gets the next section in the form.
5 | * @param {Object[]} formConfigurations A configurations of the form with the list of field in each section.
6 | * @param {String} currentForm Name of the current form.
7 | */
8 | const getNextFormName = (formConfiguration, currentForm) => {
9 | // Index of current form (currentForm) in the list of forms (formConfiguration)
10 | const index = formConfiguration.findIndex(
11 | (form) => toKebabCase(form.displayName) === currentForm
12 | )
13 |
14 | // If current form is not found or last in the list, returns first form name
15 | if ((index === -1) || (index + 1 === formConfiguration.length)) {
16 | return formConfiguration[0].displayName
17 | }
18 |
19 | // Next form in the list
20 | const nextForm = formConfiguration[index + 1]
21 |
22 | return nextForm.displayName
23 | }
24 |
25 | export default getNextFormName
26 |
--------------------------------------------------------------------------------
/static/src/js/utils/getTagCount.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Given a formData with a an array, getTagCount will return the length
3 | * of of the items
4 | * @param {Object} formData An Object with the data
5 | */
6 | const getTagCount = (formData) => {
7 | if (!formData) return 0
8 |
9 | const { items: tagItems } = formData
10 |
11 | return tagItems.length
12 | }
13 |
14 | export default getTagCount
15 |
--------------------------------------------------------------------------------
/static/src/js/utils/getTemplate.js:
--------------------------------------------------------------------------------
1 | import { getApplicationConfig } from '../../../../sharedUtils/getConfig'
2 |
3 | /**
4 | * Calls /providers/{providerId}/template/{id} lambda to get a single template
5 | * @param {Object} token A users token
6 | * @param {string} id An id for a collection template
7 | */
8 | const getTemplate = async (token, id) => {
9 | const { apiHost } = getApplicationConfig()
10 |
11 | try {
12 | const response = await fetch(`${apiHost}/templates/${id}`, {
13 | method: 'GET',
14 | headers: {
15 | Authorization: `Bearer ${token}`
16 | }
17 | })
18 | const data = await response.json()
19 |
20 | return { response: data }
21 | } catch (e) {
22 | return {
23 | error: 'Error retrieving template'
24 | }
25 | }
26 | }
27 |
28 | export default getTemplate
29 |
--------------------------------------------------------------------------------
/static/src/js/utils/getTemplates.js:
--------------------------------------------------------------------------------
1 | import { getApplicationConfig } from '../../../../sharedUtils/getConfig'
2 |
3 | /**
4 | * Calls /templates lambda to get list of all templates
5 | * @param {Object} token A users token
6 | */
7 | const getTemplates = async (token) => {
8 | const { apiHost } = getApplicationConfig()
9 |
10 | try {
11 | const response = await fetch(`${apiHost}/templates`, {
12 | method: 'GET',
13 | headers: {
14 | Authorization: `Bearer ${token}`
15 | }
16 | })
17 | const data = await response.json()
18 |
19 | return { response: data }
20 | } catch (e) {
21 | return {
22 | error: 'Error retrieving templates'
23 | }
24 | }
25 | }
26 |
27 | export default getTemplates
28 |
--------------------------------------------------------------------------------
/static/src/js/utils/getUiSchema.js:
--------------------------------------------------------------------------------
1 | import citationUiSchema from '../schemas/uiSchemas/citations'
2 | import collectionsUiSchema from '../schemas/uiSchemas/collections'
3 | import serviceUiSchema from '../schemas/uiSchemas/services'
4 | import toolsUiSchema from '../schemas/uiSchemas/tools'
5 | import variableUiSchema from '../schemas/uiSchemas/variables'
6 | import visualizationUiSchema from '../schemas/uiSchemas/visualizations'
7 |
8 | /**
9 | * Returns the UI Schema of the provided conceptType
10 | * @param {String} conceptType Concept Type of the schema to return
11 | */
12 | const getUiSchema = (conceptType) => {
13 | switch (conceptType) {
14 | case 'Citation':
15 | return citationUiSchema
16 | case 'Collection':
17 | return collectionsUiSchema
18 | case 'Service':
19 | return serviceUiSchema
20 | case 'Tool':
21 | return toolsUiSchema
22 | case 'Variable':
23 | return variableUiSchema
24 | case 'Visualization':
25 | return visualizationUiSchema
26 | default:
27 | return null
28 | }
29 | }
30 |
31 | export default getUiSchema
32 |
--------------------------------------------------------------------------------
/static/src/js/utils/getUmmSchema.js:
--------------------------------------------------------------------------------
1 | import ummCSchema from '../schemas/umm/ummCSchema'
2 | import ummSSchema from '../schemas/umm/ummSSchema'
3 | import ummTSchema from '../schemas/umm/ummTSchema'
4 | import ummVarSchema from '../schemas/umm/ummVarSchema'
5 | import otherSchemasCitSchema from '../schemas/otherSchemasCitSchema'
6 | import otherSchemasVisSchema from '../schemas/otherSchemasVisSchema'
7 |
8 | /**
9 | * Returns the UMM Schema of the provided conceptType
10 | * @param {String} conceptType Concept Type of the schema to return
11 | */
12 | const getUmmSchema = (conceptType) => {
13 | switch (conceptType) {
14 | case 'Citation':
15 | return otherSchemasCitSchema
16 | case 'Collection':
17 | return ummCSchema
18 | case 'Service':
19 | return ummSSchema
20 | case 'Tool':
21 | return ummTSchema
22 | case 'Variable':
23 | return ummVarSchema
24 | case 'Visualization':
25 | return otherSchemasVisSchema
26 | default:
27 | return null
28 | }
29 | }
30 |
31 | export default getUmmSchema
32 |
--------------------------------------------------------------------------------
/static/src/js/utils/getUmmVersion.js:
--------------------------------------------------------------------------------
1 | import { getUmmVersionsConfig } from '../../../../sharedUtils/getConfig'
2 |
3 | /**
4 | * Find the umm version based on the provided concept type
5 | * @param {String} conceptId concept type to determine the concept type
6 | */
7 | const getUmmVersion = (conceptType) => {
8 | const ummVersion = getUmmVersionsConfig()
9 |
10 | switch (conceptType) {
11 | case 'Citation':
12 | return ummVersion.ummCit
13 | case 'Collection':
14 | return ummVersion.ummC
15 | case 'Service':
16 | return ummVersion.ummS
17 | case 'Tool':
18 | return ummVersion.ummT
19 | case 'Variable':
20 | return ummVersion.ummV
21 | case 'Visualization':
22 | return ummVersion.ummVis
23 | default:
24 | return null
25 | }
26 | }
27 |
28 | export default getUmmVersion
29 |
--------------------------------------------------------------------------------
/static/src/js/utils/getVersionName.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Determines the version name based on the provided version object.
3 | *
4 | * @param {Object} version - The version object containing version information.
5 | * @param {string} version.version - The actual version string.
6 | * @param {string} version.version_type - The type of the version (e.g., 'published').
7 | * @returns {string} The determined version name.
8 | */
9 | export const getVersionName = (version) => {
10 | if (!version) return null
11 |
12 | return version.version_type === 'published' ? 'published' : version.version
13 | }
14 |
--------------------------------------------------------------------------------
/static/src/js/utils/isTokenExpired.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Is the tokenExpires value in the past
3 | * @param {Date} tokenExpires
4 | * @returns {Boolean}
5 | */
6 | const isTokenExpired = (tokenExpires = 0) => tokenExpires < new Date().getTime()
7 |
8 | export default isTokenExpired
9 |
--------------------------------------------------------------------------------
/static/src/js/utils/parseError.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Parse out an error message thrown by GraphQL
3 | * @param {Object} error Error object thrown by GraphQL
4 | */
5 | const parseError = (error) => {
6 | const {
7 | graphQLErrors = [],
8 | networkError
9 | } = error
10 |
11 | let message
12 |
13 | if (graphQLErrors.length > 0) {
14 | message = graphQLErrors.map((graphQLError) => graphQLError.message).join('\n')
15 | }
16 |
17 | if (networkError) {
18 | ({ message } = networkError)
19 | }
20 |
21 | if (message) return message
22 |
23 | return error
24 | }
25 |
26 | export default parseError
27 |
--------------------------------------------------------------------------------
/static/src/js/utils/prefixProperty.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns the given property prefixed by a `.`
3 | * @param {String} property Property to prefix
4 | */
5 | const prefixProperty = (property) => {
6 | if (property.startsWith('.')) return property
7 |
8 | return `.${property}`
9 | }
10 |
11 | export default prefixProperty
12 |
--------------------------------------------------------------------------------
/static/src/js/utils/refreshToken.js:
--------------------------------------------------------------------------------
1 | import { getApplicationConfig } from '../../../../sharedUtils/getConfig'
2 |
3 | /**
4 | * Calls refreshToken lambda to request a new token since the current one is about to expire. The new token is set as the MMT cookie
5 | * @param {Object} params
6 | * @param {String} params.jwt The user's MMT JWT
7 | * @param {Function} params.setToken Function to update the token
8 | */
9 | const refreshToken = async ({
10 | jwt,
11 | setToken
12 | }) => {
13 | const { apiHost } = getApplicationConfig()
14 |
15 | const options = {
16 | credentials: 'include',
17 | headers: {
18 | Authorization: jwt
19 | },
20 | method: 'POST'
21 | }
22 |
23 | await fetch(`${apiHost}/saml-refresh-token`, (options)).then((response) => {
24 | // If the refresh token failed, log out the user
25 | if (!response.ok) {
26 | setToken(null)
27 |
28 | window.location.href = '/'
29 | }
30 | })
31 | }
32 |
33 | export default refreshToken
34 |
--------------------------------------------------------------------------------
/static/src/js/utils/removeEmpty.js:
--------------------------------------------------------------------------------
1 | import compactDeep from 'compact-object-deep'
2 |
3 | /**
4 | * The compactDeep function takes a second argument allowing the caller to override what should be considered a empty value,
5 | * so in this case, if the "type" of the value is boolean, we are telling it NOT to consider it an empty value,
6 | * otherwise false would be considered empty with the default implementation."
7 | * @param {Object} obj A draft with ummMetadata
8 | */
9 | const removeEmpty = (obj) => compactDeep(obj, (val) => {
10 | if (typeof val === 'boolean') { return val }
11 |
12 | return undefined
13 | })
14 |
15 | export default removeEmpty
16 |
--------------------------------------------------------------------------------
/static/src/js/utils/removeMetadataKeys.js:
--------------------------------------------------------------------------------
1 | import { cloneDeep } from 'lodash-es'
2 |
3 | /**
4 | * Given a ummMetadata object, removes all the keys that are given in the
5 | * keys array.
6 | * @param {Object} metadata An object containing the keys and value
7 | * @param {Array} keys An array that has all the keys that needs to be removed
8 | */
9 | const removeMetadataKeys = (metadata, keys) => {
10 | const modifiedMetadata = cloneDeep(metadata)
11 |
12 | Object.keys(metadata).forEach((key) => {
13 | if (keys.includes(key)) {
14 | delete modifiedMetadata[key]
15 | }
16 | })
17 |
18 | return modifiedMetadata
19 | }
20 |
21 | export default removeMetadataKeys
22 |
--------------------------------------------------------------------------------
/static/src/js/utils/sendKeywordRecommendationsFeedback.js:
--------------------------------------------------------------------------------
1 | import { getApplicationConfig } from '../../../../sharedUtils/getConfig'
2 |
3 | /**
4 | * Calls gkrSendFeedback lambda /gkr-send-feedback to provide feedback on keyword recommendations
5 | * in order to help train the model
6 | * @param {Object} requestUuid the GKR request id
7 | * @param {Object} recommendations of the form {uuid:boolean} telling the API whether the uuid was accepted
8 | * @param {Array} newKeywords the new keywords not recommended
9 | */
10 | const sendKeywordRecommendationsFeedback = async (requestUuid, recommendations, newKeywords) => {
11 | const payload = {
12 | recommendations,
13 | keywords: newKeywords
14 | }
15 |
16 | const { apiHost } = getApplicationConfig()
17 |
18 | const options = {
19 | method: 'PUT',
20 | body: JSON.stringify(payload)
21 | }
22 |
23 | return fetch(`${apiHost}/gkr-send-feedback?uuid=${requestUuid}`, (options))
24 | .then((response) => response.json())
25 | .catch((err) => err)
26 | }
27 |
28 | export default sendKeywordRecommendationsFeedback
29 |
--------------------------------------------------------------------------------
/static/src/js/utils/shouldFocusField.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Function to determine if a field should focus
3 | * @param {String} focusField A name of the field that should focus
4 | * @param {String} id A id for the field
5 | */
6 | const shouldFocusField = (focusField, id) => {
7 | // Parses out first '_' if present
8 | const parsedId = id.replace(/^_/, '')
9 | if (focusField === parsedId) {
10 | return true
11 | }
12 |
13 | if (focusField && parsedId.match(/^\w+_\d+$/)) {
14 | if (parsedId !== '' && parsedId.startsWith(focusField)) {
15 | return true
16 | }
17 | }
18 |
19 | return false
20 | }
21 |
22 | export default shouldFocusField
23 |
--------------------------------------------------------------------------------
/static/src/js/utils/shouldHideGetData.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Function to determine whether to hide 'GetData'
3 | * @param {Object} props An form object from uiSchema
4 | */
5 | const shouldHideGetData = (props) => {
6 | const { URLContentType, Type } = props
7 |
8 | if (URLContentType === 'DistributionURL' && (Type === 'GET DATA' || Type === 'GET CAPABILITIES')) {
9 | return false // Do not hide 'GetData'
10 | }
11 |
12 | return true // Hide 'GetData'
13 | }
14 |
15 | export default shouldHideGetData
16 |
--------------------------------------------------------------------------------
/static/src/js/utils/shouldHideGetService.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Function to determine whether to hide 'GetService'
3 | * @param {Object} props An form object from uiSchema
4 | */
5 | const shouldHideGetService = (props) => {
6 | const { URLContentType, Type } = props
7 |
8 | if (URLContentType === 'DistributionURL' && Type === 'USE SERVICE API') {
9 | return false // Do not hide 'USE SERVICE API'
10 | }
11 |
12 | return true // Hide 'USE SERVICE API'
13 | }
14 |
15 | export default shouldHideGetService
16 |
--------------------------------------------------------------------------------
/static/src/js/utils/toKebabCase.js:
--------------------------------------------------------------------------------
1 | import { kebabCase } from 'lodash-es'
2 |
3 | /**
4 | * Returned a kebabCased version of the display name.
5 | * This special kebabCase is needed because if the string is
6 | * "Related URLs", the kebabCase from lodash converts to related-ur-ls
7 | * e.x: toKebabCase("Related URLs") -> related-urls
8 | * @param {string} displayName Name of the field that needs to be parsed
9 | * @returns {string} kebab case of the display name
10 | */
11 | const toKebabCase = (displayName) => {
12 | if (displayName === 'Related URLs') {
13 | return 'related-urls'
14 | }
15 |
16 | return kebabCase(displayName)
17 | }
18 |
19 | export default toKebabCase
20 |
--------------------------------------------------------------------------------
/static/src/js/utils/toTitleCase.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Converts a string to title case.
3 | *
4 | * @param {String} str The string to convert.
5 | * @returns {String} The converted string.
6 | */
7 | const toTitleCase = (str) => (str[0].toLowerCase() + str.slice(1))
8 | .replace(/([-])/g, ' ')
9 | .replace(/([A-Z-])/g, ' $1')
10 | .split(' ')
11 | .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
12 | .join(' ')
13 |
14 | export default toTitleCase
15 |
--------------------------------------------------------------------------------
/static/src/js/utils/traverseArray.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Given keyword object, e.g.,
3 | * {
4 | * DistributionURL: {
5 | * 'DOWNLOAD SOFTWARE': {
6 | * 'MOBILE APP': {}
7 | * }
8 | * }
9 | * }
10 | * and array of tokens, e.g.,
11 | * ['DistributionURL', 'GOTO WEB TOOL', 'LIVE ACCESS SERVER (LAS)']
12 | * it will add elements to the map base on given tokens, e.g.,
13 | * {
14 | * DistributionURL: {
15 | * 'DOWNLOAD SOFTWARE': {
16 | * 'MOBILE APP': {}
17 | * },
18 | * 'GOTO WEB TOOL': {
19 | * 'LIVE ACCESS SERVER (LAS)': {}
20 | * }
21 | * }
22 | * }
23 | * @param {Object} parent Existing object
24 | * @param {Array} tokens Array of tokens
25 | */
26 | const traverseArrays = (parent, tokens) => {
27 | if (tokens.length > 0) {
28 | const token = tokens.shift()
29 |
30 | if (token === '') {
31 | traverseArrays(parent, tokens)
32 |
33 | return
34 | }
35 |
36 | if (!parent[token]) {
37 | const p = parent
38 | p[token] = {}
39 | }
40 |
41 | traverseArrays(parent[token], tokens)
42 | }
43 | }
44 |
45 | export default traverseArrays
46 |
--------------------------------------------------------------------------------
/static/src/js/utils/updateTemplate.js:
--------------------------------------------------------------------------------
1 | import { getApplicationConfig } from '../../../../sharedUtils/getConfig'
2 |
3 | /**
4 | * Calls /providers/{providerId}/templates/{id} lambda to get update a template
5 | * @param {string} providerId A provider id that a given user is using
6 | * @param {Object} token A users token
7 | * @param {Object} ummMetadata An object with the metadata key value pairs
8 | * @param {string} id An id for a collection template
9 | */
10 | const updateTemplate = async (providerId, token, ummMetadata, id) => {
11 | const { apiHost } = getApplicationConfig()
12 |
13 | try {
14 | const response = await fetch(`${apiHost}/providers/${providerId}/templates/${id}`, {
15 | method: 'PUT',
16 | headers: {
17 | Authorization: `Bearer ${token}`
18 | },
19 | body: JSON.stringify({
20 | ...ummMetadata
21 | })
22 | })
23 |
24 | if (response.ok) {
25 | return response
26 | }
27 |
28 | throw new Error('Failed to update template')
29 | } catch (e) {
30 | return {
31 | error: 'Error updating template'
32 | }
33 | }
34 | }
35 |
36 | export default updateTemplate
37 |
--------------------------------------------------------------------------------
/static/src/js/utils/validGroupItems.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns array of valid group items: item has id or userType
3 | * @param {groupItems} groupItems
4 | * @returns groupItems
5 | */
6 | const validGroupItems = (groupItems) => groupItems?.filter((item) => item.id || item.userType)
7 | export default validGroupItems
8 |
--------------------------------------------------------------------------------
/static/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './js/App'
4 |
5 | ReactDOM.createRoot(document.getElementById('root')).render()
6 |
--------------------------------------------------------------------------------
/test-setup.js:
--------------------------------------------------------------------------------
1 | import { expect } from 'vitest'
2 | import * as matchers from '@testing-library/jest-dom/matchers'
3 |
4 | expect.extend(matchers)
5 |
6 | global.fetch = vi.fn()
7 |
8 | // JS Dom does not have scrollIntoView, so create it here
9 | // https://stackoverflow.com/a/53294906
10 | window.HTMLElement.prototype.scrollIntoView = vi.fn()
11 | window.scroll = vi.fn()
12 |
13 | vi.mock('lodash-es', async () => ({
14 | ...await vi.importActual('lodash-es'),
15 | // Don't need to wait around for debounce in tests
16 | debounce: vi.fn((fn) => fn)
17 | }))
18 |
19 | vi.mock('uuid', () => ({
20 | v4: () => 'mock-uuid'
21 | }))
22 |
23 | Object.defineProperty(globalThis, 'uuid', {
24 | value: {
25 | v4: () => 'mock-uuid'
26 | }
27 | })
28 |
--------------------------------------------------------------------------------