├── .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 |
5 |
8 |
11 |
15 |
16 |
17 |
18 | `; 19 | 20 | exports[`Error Banner Component Provided a test id it renders a loading banner 1`] = ` 21 |
22 |
25 |
28 |
32 |
33 |
34 |
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 |
5 |
8 |
11 |
15 |
16 |
17 |
18 | `; 19 | 20 | exports[`Error Banner Component > Provided a test id > it renders a loading banner 1`] = ` 21 |
22 |
25 |
28 |
32 |
33 |
34 |
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 | --------------------------------------------------------------------------------