diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 9df427eea..111133a25 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -2,6 +2,7 @@ import type { Preview } from '@storybook/react' import { ThemeProvider } from '@iqss/dataverse-design-system' import { MemoryRouter } from 'react-router-dom' import { FakerHelper } from '../tests/component/shared/FakerHelper' +import 'react-loading-skeleton/dist/skeleton.css' const preview: Preview = { parameters: { diff --git a/package-lock.json b/package-lock.json index 004f5610f..1bdce50e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "dependencies": { "@faker-js/faker": "7.6.0", - "@iqss/dataverse-client-javascript": "2.0.0-pr141.153a56a", + "@iqss/dataverse-client-javascript": "2.0.0-pr155.be01a3f", "@iqss/dataverse-design-system": "*", "@istanbuljs/nyc-config-typescript": "1.0.2", "@tanstack/react-table": "8.9.2", @@ -34,7 +34,7 @@ "react-infinite-scroll-hook": "4.1.1", "react-loader-spinner": "5.3.4", "react-markdown": "8.0.7", - "react-router-dom": "6.8.1", + "react-router-dom": "6.23.1", "react-topbar-progress-indicator": "4.1.1", "sass": "1.58.1", "typescript": "4.9.5", @@ -3610,14 +3610,14 @@ }, "node_modules/@iqss/dataverse-client-javascript": { "name": "@IQSS/dataverse-client-javascript", - "version": "2.0.0-pr141.153a56a", - "resolved": "https://npm.pkg.github.com/download/@IQSS/dataverse-client-javascript/2.0.0-pr141.153a56a/cb16d9477207d284110ca3c348b6540274509cc9", - "integrity": "sha512-urN99q1ll1Xiqr2wDsG9wvLcAjrxsOZmLXmCgFp6zTFoFHaVZ8c07k0EqQZk4jUv6CwgQY8j+jhmW+T+ABuj6A==", + "version": "2.0.0-pr155.be01a3f", + "resolved": "https://npm.pkg.github.com/download/@IQSS/dataverse-client-javascript/2.0.0-pr155.be01a3f/dea4dac3ef3aab1802b444ccc08326843c530d80", + "integrity": "sha512-Q2KhE3JGu3BaTD1TB3l0iQ6yZIvpUOkds8Zk0nezNymm4pL8gjdISomJDUXFFW9tQuAZ0PkXuOEQGmVfzEUKNA==", "license": "MIT", "dependencies": { "@types/node": "^18.15.11", "@types/turndown": "^5.0.1", - "axios": "^1.3.4", + "axios": "^1.7.2", "turndown": "^7.1.2", "typescript": "^4.9.5" } @@ -3627,6 +3627,16 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.19.tgz", "integrity": "sha512-+pMhShR3Or5GR0/sp4Da7FnhVmTalWm81M6MkEldbwjETSaPalw138Z4KdpQaistvqQxLB7Cy4xwYdxpbSOs9Q==" }, + "node_modules/@iqss/dataverse-client-javascript/node_modules/axios": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/@iqss/dataverse-design-system": { "resolved": "packages/design-system", "link": true @@ -5988,11 +5998,11 @@ } }, "node_modules/@remix-run/router": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.2.tgz", - "integrity": "sha512-t54ONhl/h75X94SWsHGQ4G/ZrCEguKSRQr7DrjTciJXW0YU1QhlwYeycvK5JgkzlxmvrK7wq1NB/PLtHxoiDcA==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz", + "integrity": "sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig==", "engines": { - "node": ">=14" + "node": ">=14.0.0" } }, "node_modules/@restart/hooks": { @@ -17927,6 +17937,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "dev": true, "dependencies": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -23534,9 +23545,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", @@ -35822,29 +35833,29 @@ } }, "node_modules/react-router": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.1.tgz", - "integrity": "sha512-Jgi8BzAJQ8MkPt8ipXnR73rnD7EmZ0HFFb7jdQU24TynGW1Ooqin2KVDN9voSC+7xhqbbCd2cjGUepb6RObnyg==", + "version": "6.23.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.23.1.tgz", + "integrity": "sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ==", "dependencies": { - "@remix-run/router": "1.3.2" + "@remix-run/router": "1.16.1" }, "engines": { - "node": ">=14" + "node": ">=14.0.0" }, "peerDependencies": { "react": ">=16.8" } }, "node_modules/react-router-dom": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.8.1.tgz", - "integrity": "sha512-67EXNfkQgf34P7+PSb6VlBuaacGhkKn3kpE51+P6zYSG2kiRoumXEL6e27zTa9+PGF2MNXbgIUHTVlleLbIcHQ==", + "version": "6.23.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.23.1.tgz", + "integrity": "sha512-utP+K+aSTtEdbWpC+4gxhdlPFwuEfDKq8ZrPFU65bbRJY+l706qjR7yaidBpo3MSeA/fzwbXWbKBI6ftOnP3OQ==", "dependencies": { - "@remix-run/router": "1.3.2", - "react-router": "6.8.1" + "@remix-run/router": "1.16.1", + "react-router": "6.23.1" }, "engines": { - "node": ">=14" + "node": ">=14.0.0" }, "peerDependencies": { "react": ">=16.8", diff --git a/package.json b/package.json index 7aece5b0d..bf43ed983 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ }, "dependencies": { "@faker-js/faker": "7.6.0", - "@iqss/dataverse-client-javascript": "2.0.0-pr141.153a56a", + "@iqss/dataverse-client-javascript": "2.0.0-pr155.be01a3f", "@iqss/dataverse-design-system": "*", "@istanbuljs/nyc-config-typescript": "1.0.2", "@tanstack/react-table": "8.9.2", @@ -38,7 +38,7 @@ "react-infinite-scroll-hook": "4.1.1", "react-loader-spinner": "5.3.4", "react-markdown": "8.0.7", - "react-router-dom": "6.8.1", + "react-router-dom": "6.23.1", "react-topbar-progress-indicator": "4.1.1", "sass": "1.58.1", "typescript": "4.9.5", diff --git a/packages/design-system/src/lib/components/form/form-group/form-element/FormSelect.tsx b/packages/design-system/src/lib/components/form/form-group/form-element/FormSelect.tsx index fb99ce748..c70a3c39d 100644 --- a/packages/design-system/src/lib/components/form/form-group/form-element/FormSelect.tsx +++ b/packages/design-system/src/lib/components/form/form-group/form-element/FormSelect.tsx @@ -5,17 +5,19 @@ import * as React from 'react' export interface FormSelectProps extends Omit, 'size'> { + value?: string isInvalid?: boolean isValid?: boolean disabled?: boolean } export const FormSelect = React.forwardRef(function FormSelect( - { isInvalid, isValid, disabled, children, ...props }: PropsWithChildren, + { value, isInvalid, isValid, disabled, children, ...props }: PropsWithChildren, ref ) { return ( } {...props} /> diff --git a/public/locales/en/createDataset.json b/public/locales/en/createDataset.json index e36a9bff8..f2a64767a 100644 --- a/public/locales/en/createDataset.json +++ b/public/locales/en/createDataset.json @@ -1,43 +1,9 @@ { "pageTitle": "Create Dataset", - "metadataTip": { - "title": "Metadata Tip", - "content": "After adding the dataset, click the Edit Dataset button to add more metadata." - }, "hostCollection": { "label": "Host Collection", "description": "The collection which contains this data.", "helpText": "Changing the host collection will clear any fields you may have entered data into.", "buttonLabel": "Edit Host Collection" - }, - "requiredFields": "Asterisks indicate required fields", - "validationAlert": { - "title": "Validation Error", - "content": "Required fields were missed or there was a validation error. Please scroll down to see details." - }, - "datasetForm": { - "field": { - "required": "{{displayName}} is required", - "invalid": { - "url": "{{displayName}} is not a valid URL", - "email": "{{displayName}} is not a valid email", - "int": "{{displayName}} is not a valid integer", - "float": "{{displayName}} is not a valid float", - "date": "{{displayName}} is not a valid date. Please use the format {{dateFormat}}" - } - }, - "status": { - "submitting": "Submitting...", - "success": "Form submitted successfully!", - "failed": "Error: Submission failed." - }, - "addRowButton": "Add", - "deleteRowButton": "Delete" - }, - "datasetFormStates": { - "submitting": "Form Submitting", - "submissionSuccess": "Form submission successful" - }, - "saveButton": "Save Dataset", - "cancelButton": "Cancel" + } } diff --git a/public/locales/en/datasetMetadataForm.json b/public/locales/en/datasetMetadataForm.json new file mode 100644 index 000000000..29bb4a830 --- /dev/null +++ b/public/locales/en/datasetMetadataForm.json @@ -0,0 +1,38 @@ +{ + "metadataTip": { + "title": "Metadata Tip", + "content": "After adding the dataset, click the Edit Dataset button to add more metadata." + }, + "validationAlert": { + "title": "Validation Error", + "content": "Required fields were missed or there was a validation error. Please scroll down to see details." + }, + "requiredFields": "Asterisks indicate required fields", + "mayBecomeRequired": "One or more of these fields may become required if you add to one or more of these optional fields.", + "field": { + "required": "{{displayName}} is required", + "invalid": { + "url": "{{displayName}} is not a valid URL", + "email": "{{displayName}} is not a valid email", + "int": "{{displayName}} is not a valid integer", + "float": "{{displayName}} is not a valid float", + "date": "{{displayName}} is not a valid date. Please use the format {{dateFormat}}" + } + }, + "status": { + "submitting": "Submitting...", + "success": "Form submitted successfully!", + "failed": "Error: Submission failed." + }, + "states": { + "submitting": "Form Submitting", + "submissionSuccess": "Form submission successful" + }, + "addRowButton": "Add", + "deleteRowButton": "Delete", + "saveButton": { + "createMode": "Save Dataset", + "editMode": "Save Changes" + }, + "cancelButton": "Cancel" +} diff --git a/public/locales/en/editDatasetMetadata.json b/public/locales/en/editDatasetMetadata.json new file mode 100644 index 000000000..ee406b0da --- /dev/null +++ b/public/locales/en/editDatasetMetadata.json @@ -0,0 +1,12 @@ +{ + "breadcrumbActionItem": "Edit Dataset Metadata", + "infoAlert": { + "heading": "Edit Dataset Metadata", + "text": "Add more metadata about this dataset to help others easily find it." + }, + "hostCollection": { + "label": "Host Collection", + "description": "The collection which contains this data." + }, + "metadata": "Metadata" +} diff --git a/src/App.tsx b/src/App.tsx index 485ef0210..01602e67e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,6 +4,7 @@ import { SessionProvider } from './sections/session/SessionProvider' import { UserJSDataverseRepository } from './users/infrastructure/repositories/UserJSDataverseRepository' import { DataverseApiAuthMechanism } from '@iqss/dataverse-client-javascript/dist/core/infra/repositories/ApiConfig' import { BASE_URL } from './config' +import 'react-loading-skeleton/dist/skeleton.css' if (BASE_URL === '') { throw Error('VITE_DATAVERSE_BACKEND_URL environment variable should be specified.') diff --git a/src/Router.tsx b/src/Router.tsx index f7c2feaba..f44267638 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -7,6 +7,7 @@ import { CreateDatasetFactory } from './sections/create-dataset/CreateDatasetFac import { FileFactory } from './sections/file/FileFactory' import { CollectionFactory } from './sections/collection/CollectionFactory' import { UploadDatasetFilesFactory } from './sections/upload-dataset-files/UploadDatasetFilesFactory' +import { EditDatasetMetadataFactory } from './sections/edit-dataset-metadata/EditDatasetMetadataFactory' import { DatasetNonNumericVersion } from './dataset/domain/models/Dataset' const router = createBrowserRouter( @@ -36,6 +37,10 @@ const router = createBrowserRouter( path: Route.UPLOAD_DATASET_FILES, element: UploadDatasetFilesFactory.create() }, + { + path: Route.EDIT_DATASET_METADATA, + element: EditDatasetMetadataFactory.create() + }, { path: Route.FILES, element: FileFactory.create() diff --git a/src/dataset/domain/repositories/DatasetRepository.ts b/src/dataset/domain/repositories/DatasetRepository.ts index f86b954a4..533b2b68a 100644 --- a/src/dataset/domain/repositories/DatasetRepository.ts +++ b/src/dataset/domain/repositories/DatasetRepository.ts @@ -7,6 +7,7 @@ export interface DatasetRepository { getByPersistentId: (persistentId: string, version?: string) => Promise getByPrivateUrlToken: (privateUrlToken: string) => Promise create: (dataset: DatasetDTO, collectionId?: string) => Promise<{ persistentId: string }> + updateMetadata: (datasetId: string | number, datasetDTO: DatasetDTO) => Promise getAllWithCount: ( collectionId: string, paginationInfo: DatasetPaginationInfo diff --git a/src/dataset/domain/useCases/updateDatasetMetadata.ts b/src/dataset/domain/useCases/updateDatasetMetadata.ts new file mode 100644 index 000000000..2f661c31f --- /dev/null +++ b/src/dataset/domain/useCases/updateDatasetMetadata.ts @@ -0,0 +1,12 @@ +import { DatasetRepository } from '../repositories/DatasetRepository' +import { DatasetDTO } from './DTOs/DatasetDTO' + +export function updateDatasetMetadata( + datasetRepository: DatasetRepository, + datasetId: string | number, + updatedDataset: DatasetDTO +): Promise { + return datasetRepository.updateMetadata(datasetId, updatedDataset).catch((error: Error) => { + throw new Error(error.message) + }) +} diff --git a/src/dataset/infrastructure/mappers/DatasetDTOMapper.ts b/src/dataset/infrastructure/mappers/DatasetDTOMapper.ts index c8558cf11..57b8bf70a 100644 --- a/src/dataset/infrastructure/mappers/DatasetDTOMapper.ts +++ b/src/dataset/infrastructure/mappers/DatasetDTOMapper.ts @@ -1,7 +1,7 @@ import { DatasetDTO } from '../../domain/useCases/DTOs/DatasetDTO' import { - NewDataset as JSDatasetDTO, - NewDatasetMetadataBlockValues as JSMetadataBlocksDTO + DatasetDTO as JSDatasetDTO, + DatasetMetadataBlockValuesDTO as JSMetadataBlocksDTO } from '@iqss/dataverse-client-javascript' export class DatasetDTOMapper { diff --git a/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts b/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts index b158842c2..f5164d4fb 100644 --- a/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts +++ b/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts @@ -19,7 +19,8 @@ import { DatasetPreviewSubset, createDataset, CreatedDatasetIdentifiers as JSDatasetIdentifiers, - WriteError + WriteError, + updateDataset } from '@iqss/dataverse-client-javascript' import { JSDatasetMapper } from '../mappers/JSDatasetMapper' import { DatasetPaginationInfo } from '../../domain/models/DatasetPaginationInfo' @@ -193,4 +194,12 @@ export class DatasetJSDataverseRepository implements DatasetRepository { throw new Error(error.message) }) } + + updateMetadata(datasetId: string | number, updatedDataset: DatasetDTO): Promise { + return updateDataset + .execute(datasetId, DatasetDTOMapper.toJSDatasetDTO(updatedDataset)) + .catch((error: WriteError) => { + throw new Error(error.message) + }) + } } diff --git a/src/index.tsx b/src/index.tsx index acdea4ba3..ba839fec8 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,7 +1,6 @@ import React from 'react' import ReactDOM from 'react-dom/client' import App from './App' -import reportWebVitals from './reportWebVitals' import './i18n' import { LoadingProvider } from './sections/loading/LoadingProvider' import { ThemeProvider } from '@iqss/dataverse-design-system' @@ -18,8 +17,3 @@ root.render( ) - -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(console.log) diff --git a/src/metadata-block-info/domain/models/MetadataBlockInfo.ts b/src/metadata-block-info/domain/models/MetadataBlockInfo.ts index 9c29e7da4..4e2cd657d 100644 --- a/src/metadata-block-info/domain/models/MetadataBlockInfo.ts +++ b/src/metadata-block-info/domain/models/MetadataBlockInfo.ts @@ -1,3 +1,5 @@ +import { DatasetMetadataFieldValue } from '../../../dataset/domain/models/Dataset' + export interface MetadataBlockInfo { id: number name: string @@ -6,6 +8,10 @@ export interface MetadataBlockInfo { displayOnCreate: boolean } +export interface MetadataBlockInfoWithMaybeValues extends MetadataBlockInfo { + metadataFields: Record +} + export interface MetadataField { name: string displayName: string @@ -24,6 +30,10 @@ export interface MetadataField { displayOnCreate: boolean } +export interface MetadataFieldWithMaybeValue extends MetadataField { + value?: DatasetMetadataFieldValue +} + export const TypeMetadataFieldOptions = { Date: 'DATE', Email: 'EMAIL', diff --git a/src/sections/Route.enum.ts b/src/sections/Route.enum.ts index bdce8e57d..ab6c4c0d4 100644 --- a/src/sections/Route.enum.ts +++ b/src/sections/Route.enum.ts @@ -6,6 +6,7 @@ export enum Route { DATASETS = '/datasets', CREATE_DATASET = '/datasets/create', UPLOAD_DATASET_FILES = '/datasets/upload-files', + EDIT_DATASET_METADATA = '/datasets/edit-metadata', FILES = '/files', COLLECTIONS = '/collections' } diff --git a/src/sections/collection/Collection.tsx b/src/sections/collection/Collection.tsx index 63b36cade..473c2bea4 100644 --- a/src/sections/collection/Collection.tsx +++ b/src/sections/collection/Collection.tsx @@ -46,7 +46,7 @@ export function Collection({ {user && (
- +
)} diff --git a/src/sections/collection/datasets-list/DatasetsListWithInfiniteScroll.tsx b/src/sections/collection/datasets-list/DatasetsListWithInfiniteScroll.tsx index d757ccbb4..05bf93fa0 100644 --- a/src/sections/collection/datasets-list/DatasetsListWithInfiniteScroll.tsx +++ b/src/sections/collection/datasets-list/DatasetsListWithInfiniteScroll.tsx @@ -12,7 +12,6 @@ import { InitialLoadingSkeleton, LoadingSkeleton } from './DatasetsListWithInfin import { ErrorDatasetsMessage } from './ErrorDatasetsMessage' import { NO_DATASETS, useLoadDatasets } from './useLoadDatasets' import styles from './DatasetsList.module.scss' -import 'react-loading-skeleton/dist/skeleton.css' interface DatasetsListWithInfiniteScrollProps { datasetRepository: DatasetRepository diff --git a/src/sections/create-dataset/CreateDataset.tsx b/src/sections/create-dataset/CreateDataset.tsx index 14e2dbe77..4a5186146 100644 --- a/src/sections/create-dataset/CreateDataset.tsx +++ b/src/sections/create-dataset/CreateDataset.tsx @@ -1,47 +1,25 @@ -import { useEffect } from 'react' import { useTranslation } from 'react-i18next' -import { useLoading } from '../loading/LoadingContext' -import { useGetMetadataBlocksInfo } from './useGetMetadataBlocksInfo' -import { MetadataFieldsHelper } from './MetadataFieldsHelper' import { type DatasetRepository } from '../../dataset/domain/repositories/DatasetRepository' import { type MetadataBlockInfoRepository } from '../../metadata-block-info/domain/repositories/MetadataBlockInfoRepository' import { SeparationLine } from '../shared/layout/SeparationLine/SeparationLine' -import { DatasetForm } from './DatasetForm' -import { DatasetFormSkeleton } from './DatasetFormSkeleton' import { HostCollectionForm } from './HostCollectionForm/HostCollectionForm' import { NotImplementedModal } from '../not-implemented/NotImplementedModal' import { useNotImplementedModal } from '../not-implemented/NotImplementedModalContext' +import { DatasetMetadataForm } from '../shared/form/DatasetMetadataForm' interface CreateDatasetProps { - repository: DatasetRepository + datasetRepository: DatasetRepository metadataBlockInfoRepository: MetadataBlockInfoRepository collectionId?: string } export function CreateDataset({ - repository, + datasetRepository, metadataBlockInfoRepository, collectionId = 'root' }: CreateDatasetProps) { const { t } = useTranslation('createDataset') - const { isLoading, setIsLoading } = useLoading() const { isModalOpen, hideModal } = useNotImplementedModal() - const { - metadataBlocks, - isLoading: isLoadingMetadataBlocksConfiguration, - error: errorLoadingMetadataBlocksToRender - } = useGetMetadataBlocksInfo({ - metadataBlockInfoRepository, - collectionId - }) - - const formDefaultValues = MetadataFieldsHelper.getFormDefaultValues(metadataBlocks) - - useEffect(() => { - if (!isLoadingMetadataBlocksConfiguration) { - setIsLoading(false) - } - }, [isLoading, isLoadingMetadataBlocksConfiguration, setIsLoading]) return ( <> @@ -53,17 +31,12 @@ export function CreateDataset({ - {isLoadingMetadataBlocksConfiguration ? ( - - ) : ( - - )} + ) diff --git a/src/sections/create-dataset/CreateDatasetFactory.tsx b/src/sections/create-dataset/CreateDatasetFactory.tsx index 1d8675683..2018b883e 100644 --- a/src/sections/create-dataset/CreateDatasetFactory.tsx +++ b/src/sections/create-dataset/CreateDatasetFactory.tsx @@ -5,7 +5,7 @@ import { DatasetJSDataverseRepository } from '../../dataset/infrastructure/repos import { MetadataBlockInfoJSDataverseRepository } from '../../metadata-block-info/infrastructure/repositories/MetadataBlockInfoJSDataverseRepository' import { NotImplementedModalProvider } from '../not-implemented/NotImplementedModalProvider' -const repository = new DatasetJSDataverseRepository() +const datasetRepository = new DatasetJSDataverseRepository() const metadataBlockInfoRepository = new MetadataBlockInfoJSDataverseRepository() export class CreateDatasetFactory { @@ -24,7 +24,7 @@ function CreateDatasetWithSearchParams() { return ( diff --git a/src/sections/create-dataset/DatasetForm.module.scss b/src/sections/create-dataset/DatasetForm.module.scss deleted file mode 100644 index 9e1bd7c5d..000000000 --- a/src/sections/create-dataset/DatasetForm.module.scss +++ /dev/null @@ -1,3 +0,0 @@ -.form-container { - scroll-margin-top: 62px; -} diff --git a/src/sections/create-dataset/DatasetForm.tsx b/src/sections/create-dataset/DatasetForm.tsx deleted file mode 100644 index 4b3ac6983..000000000 --- a/src/sections/create-dataset/DatasetForm.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import { MouseEvent, useEffect, useMemo, useRef } from 'react' -import { useNavigate } from 'react-router-dom' -import { FieldErrors, FormProvider, useForm } from 'react-hook-form' -import { useTranslation } from 'react-i18next' -import { SubmissionStatus, useCreateDatasetForm } from './useCreateDatasetForm' -import { type DatasetRepository } from '../../dataset/domain/repositories/DatasetRepository' -import { type MetadataBlockInfo } from '../../metadata-block-info/domain/models/MetadataBlockInfo' -import { type CreateDatasetFormValues } from './MetadataFieldsHelper' -import { Form, Accordion, Alert, Button } from '@iqss/dataverse-design-system' -import { RequiredFieldText } from '../shared/form/RequiredFieldText/RequiredFieldText' -import { SeparationLine } from '../shared/layout/SeparationLine/SeparationLine' -import { MetadataBlockFormFields } from './MetadataBlockFormFields' -import { Route } from '../Route.enum' -import styles from './DatasetForm.module.scss' -import { useSession } from '../session/SessionContext' - -interface DatasetFormProps { - repository: DatasetRepository - collectionId?: string - metadataBlocks: MetadataBlockInfo[] - errorLoadingMetadataBlocks: string | null - formDefaultValues: CreateDatasetFormValues -} - -export const DatasetForm = ({ - repository, - collectionId = 'root', - metadataBlocks, - errorLoadingMetadataBlocks, - formDefaultValues -}: DatasetFormProps) => { - const navigate = useNavigate() - const { t } = useTranslation('createDataset') - - const accordionRef = useRef(null) - const formContainerRef = useRef(null) - - const { submissionStatus, createError, submitForm } = useCreateDatasetForm( - repository, - collectionId, - onCreateDatasetError - ) - const { user } = useSession() - - const isErrorLoadingMetadataBlocks = Boolean(errorLoadingMetadataBlocks) - - const form = useForm({ - mode: 'onChange', - defaultValues: formDefaultValues - }) - const { setValue } = form - useEffect(() => { - if (user) { - setValue('citation.author.0.authorName', user.displayName) - setValue('citation.datasetContact.0.datasetContactName', user.displayName) - setValue('citation.datasetContact.0.datasetContactEmail', user.email, { - shouldValidate: true - }) - if (user.affiliation) { - setValue('citation.datasetContact.0.datasetContactAffiliation', user.affiliation) - setValue('citation.author.0.authorAffiliation', user.affiliation) - } - } - }, [setValue, user]) - const handleCancel = (event: MouseEvent) => { - event.preventDefault() - navigate(Route.HOME) - } - - const onInvalidSubmit = (errors: FieldErrors) => { - if (!accordionRef.current) return - /* - Get the first metadata block accordion item with an error, and if it's collapsed, open it - Only for the case when accordion is closed, otherwise focus is already handled by react-hook-form - */ - const firstMetadataBlockNameWithError = Object.keys(errors)[0] - - const accordionItemsButtons: HTMLButtonElement[] = Array.from( - accordionRef.current.querySelectorAll('button.accordion-button') - ) - - accordionItemsButtons.forEach((button) => { - const parentItem = button.closest('.accordion-item') - const itemBlockName = parentItem?.id.split('-').pop() - const buttonIsCollapsed = button.classList.contains('collapsed') - - if (itemBlockName === firstMetadataBlockNameWithError && buttonIsCollapsed) { - button.click() - - setTimeout( - /* istanbul ignore next */ () => { - const focusedElement = document.activeElement - focusedElement?.scrollIntoView({ behavior: 'smooth', block: 'center' }) - }, - 800 - ) - } - }) - } - - function onCreateDatasetError() { - if (formContainerRef.current) { - formContainerRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' }) - } - } - - const disableSubmitButton = useMemo(() => { - return isErrorLoadingMetadataBlocks || submissionStatus === SubmissionStatus.IsSubmitting - }, [isErrorLoadingMetadataBlocks, submissionStatus]) - - return ( -
- - {isErrorLoadingMetadataBlocks && ( - - {errorLoadingMetadataBlocks} - - )} - {submissionStatus === SubmissionStatus.IsSubmitting && ( -

{t('datasetForm.status.submitting')}

- )} - - {submissionStatus === SubmissionStatus.SubmitComplete && ( -

{t('datasetForm.status.success')}

- )} - {submissionStatus === SubmissionStatus.Errored && ( - - {createError} - - )} - - -
- {metadataBlocks.length > 0 && ( - - {metadataBlocks.map((metadataBlock, index) => ( - - {metadataBlock.displayName} - - - - - ))} - - )} - - - - {t('metadataTip.content')} - - - - -
-
- ) -} diff --git a/src/sections/create-dataset/DatasetFormSkeleton.tsx b/src/sections/create-dataset/DatasetFormSkeleton.tsx deleted file mode 100644 index 12b135639..000000000 --- a/src/sections/create-dataset/DatasetFormSkeleton.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import Skeleton, { SkeletonTheme } from 'react-loading-skeleton' -import { Accordion, Col, Row } from '@iqss/dataverse-design-system' -import { SeparationLine } from '../shared/layout/SeparationLine/SeparationLine' -import 'react-loading-skeleton/dist/skeleton.css' - -export const DatasetFormSkeleton = () => { - return ( - -
- - - - - - - - {Array.from({ length: 4 }).map((_, index) => ( - - - - - - - - - - {index === 2 && ( - - - - )} - - - - ))} - {Array.from({ length: 3 }).map((_, index) => ( - - - - - - - - - - - - - - - - - - - - - - - - - ))} - - - - - -
- - - -
-
-
- ) -} diff --git a/src/sections/create-dataset/MetadataBlockFormFields/MetadataFormField/Fields/ComposedField.tsx b/src/sections/create-dataset/MetadataBlockFormFields/MetadataFormField/Fields/ComposedField.tsx deleted file mode 100644 index e7e9cbbcc..000000000 --- a/src/sections/create-dataset/MetadataBlockFormFields/MetadataFormField/Fields/ComposedField.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { Col, Form, Row } from '@iqss/dataverse-design-system' -import { type MetadataField } from '../../../../../metadata-block-info/domain/models/MetadataBlockInfo' -import { MetadataFormField, type CommonFieldProps } from '..' -import styles from '../index.module.scss' - -interface ComposedFieldProps extends CommonFieldProps { - metadataBlockName: string - childMetadataFields: Record - compoundParentName?: string - fieldsArrayIndex?: number -} - -export const ComposedField = ({ - metadataBlockName, - title, - name, - description, - isRequired, - childMetadataFields -}: ComposedFieldProps) => { - return ( - - - - {Object.entries(childMetadataFields).map( - ([childMetadataFieldKey, childMetadataFieldInfo]) => { - return ( - - ) - } - )} - - - - ) -} diff --git a/src/sections/create-dataset/MetadataBlockFormFields/MetadataFormField/Fields/Vocabulary.tsx b/src/sections/create-dataset/MetadataBlockFormFields/MetadataFormField/Fields/Vocabulary.tsx deleted file mode 100644 index f80eab3c7..000000000 --- a/src/sections/create-dataset/MetadataBlockFormFields/MetadataFormField/Fields/Vocabulary.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { useMemo } from 'react' -import { Controller, useFormContext } from 'react-hook-form' -import { Form, Row, Col } from '@iqss/dataverse-design-system' -import { type CommonFieldProps } from '..' -import { MetadataFieldsHelper } from '../../../MetadataFieldsHelper' - -interface VocabularyProps extends CommonFieldProps { - metadataBlockName: string - options: string[] - compoundParentName?: string - fieldsArrayIndex?: number -} -export const Vocabulary = ({ - name, - compoundParentName, - metadataBlockName, - rulesToApply, - description, - title, - options, - isRequired, - withinMultipleFieldsGroup, - fieldsArrayIndex -}: VocabularyProps) => { - const { control } = useFormContext() - - const builtFieldName = useMemo( - () => - MetadataFieldsHelper.defineFieldName( - name, - metadataBlockName, - compoundParentName, - fieldsArrayIndex - ), - [name, metadataBlockName, compoundParentName, fieldsArrayIndex] - ) - - return ( - ( - - - {title} - - - - - - - {options.map((option) => ( - - ))} - - {error?.message} - - - - - )} - /> - ) -} diff --git a/src/sections/create-dataset/MetadataFieldsHelper.ts b/src/sections/create-dataset/MetadataFieldsHelper.ts deleted file mode 100644 index bab335bd5..000000000 --- a/src/sections/create-dataset/MetadataFieldsHelper.ts +++ /dev/null @@ -1,279 +0,0 @@ -import { - MetadataBlockInfo, - MetadataField -} from '../../metadata-block-info/domain/models/MetadataBlockInfo' -import { - DatasetDTO, - DatasetMetadataBlockValuesDTO, - DatasetMetadataChildFieldValueDTO -} from '../../dataset/domain/useCases/DTOs/DatasetDTO' - -export type CreateDatasetFormValues = Record - -export type MetadataBlockFormValues = Record< - string, - string | PrimitiveMultipleFormValue | VocabularyMultipleFormValue | ComposedFieldValues -> - -type VocabularyMultipleFormValue = string[] - -type PrimitiveMultipleFormValue = { value: string }[] - -type ComposedFieldValues = ComposedSingleFieldValue | ComposedSingleFieldValue[] - -type ComposedSingleFieldValue = Record - -export class MetadataFieldsHelper { - public static replaceDotNamesKeysWithSlash( - metadataBlocks: MetadataBlockInfo[] - ): MetadataBlockInfo[] { - for (const block of metadataBlocks) { - if (block.metadataFields) { - this.dotReplacer(block.metadataFields) - } - } - return metadataBlocks - } - - private static dotReplacer(metadataFields: Record | undefined) { - if (!metadataFields) return - - for (const key in metadataFields) { - const field = metadataFields[key] - if (field.name.includes('.')) { - field.name = field.name.replace(/\./g, '/') - } - if (field.childMetadataFields) { - this.dotReplacer(field.childMetadataFields) - } - } - } - - public static getFormDefaultValues(metadataBlocks: MetadataBlockInfo[]): CreateDatasetFormValues { - const formDefaultValues: CreateDatasetFormValues = {} - - for (const block of metadataBlocks) { - const blockValues: MetadataBlockFormValues = {} - - for (const field of Object.values(block.metadataFields)) { - const fieldName = field.name - - if (field.typeClass === 'compound') { - const childFieldsWithEmptyValues: Record = {} - - if (field.childMetadataFields) { - for (const childField of Object.values(field.childMetadataFields)) { - if (childField.typeClass === 'primitive') { - childFieldsWithEmptyValues[childField.name] = '' - } - - if (childField.typeClass === 'controlledVocabulary') { - childFieldsWithEmptyValues[childField.name] = '' - } - } - } - blockValues[fieldName] = field.multiple - ? [childFieldsWithEmptyValues] - : childFieldsWithEmptyValues - } - - if (field.typeClass === 'primitive') { - blockValues[fieldName] = field.multiple ? [{ value: '' }] : '' - } - - if (field.typeClass === 'controlledVocabulary') { - blockValues[fieldName] = field.multiple ? [] : '' - } - } - - formDefaultValues[block.name] = blockValues - } - - return formDefaultValues - } - - public static replaceSlashKeysWithDot(obj: CreateDatasetFormValues): CreateDatasetFormValues { - const formattedNewObject: CreateDatasetFormValues = {} - - for (const key in obj) { - const blockKey = key.replace(/\//g, '.') - const metadataBlockFormValues = obj[key] - - formattedNewObject[blockKey] = {} - - Object.entries(metadataBlockFormValues).forEach(([fieldName, fieldValue]) => { - const newFieldName = fieldName.replace(/\//g, '.') - - if ( - this.isPrimitiveFieldValue(fieldValue) || - this.isVocabularyMultipleFieldValue(fieldValue) || - this.isPrimitiveMultipleFieldValue(fieldValue) - ) { - formattedNewObject[blockKey][newFieldName] = fieldValue - return - } - - if (this.isComposedSingleFieldValue(fieldValue)) { - formattedNewObject[blockKey][newFieldName] = {} - Object.entries(fieldValue).forEach(([nestedFieldName, nestedFieldValue]) => { - const newNestedFieldName = nestedFieldName.replace(/\//g, '.') - const parentOfNestedField = formattedNewObject[blockKey][ - newFieldName - ] as ComposedSingleFieldValue - - parentOfNestedField[newNestedFieldName] = nestedFieldValue - }) - return - } - - if (this.isComposedMultipleFieldValue(fieldValue)) { - formattedNewObject[blockKey][newFieldName] = fieldValue.map((composedFieldValues) => { - const composedField: ComposedSingleFieldValue = {} - - Object.entries(composedFieldValues).forEach(([nestedFieldName, nestedFieldValue]) => { - const newNestedFieldName = nestedFieldName.replace(/\//g, '.') - - composedField[newNestedFieldName] = nestedFieldValue - }) - - return composedField - }) - } - }) - } - - return formattedNewObject - } - - public static formatFormValuesToCreateDatasetDTO( - formValues: CreateDatasetFormValues - ): DatasetDTO { - const metadataBlocks: DatasetDTO['metadataBlocks'] = [] - - for (const metadataBlockName in formValues) { - const formattedMetadataBlock: DatasetMetadataBlockValuesDTO = { - name: metadataBlockName, - fields: {} - } - const metadataBlockFormValues = formValues[metadataBlockName] - - Object.entries(metadataBlockFormValues).forEach(([fieldName, fieldValue]) => { - if (this.isPrimitiveFieldValue(fieldValue)) { - if (fieldValue !== '') { - formattedMetadataBlock.fields[fieldName] = fieldValue - return - } - return - } - if (this.isVocabularyMultipleFieldValue(fieldValue)) { - if (fieldValue.length > 0) { - formattedMetadataBlock.fields[fieldName] = fieldValue - return - } - return - } - - if (this.isPrimitiveMultipleFieldValue(fieldValue)) { - const primitiveMultipleFieldValues = fieldValue - .map((primitiveField) => primitiveField.value) - .filter((v) => v !== '') - - if (primitiveMultipleFieldValues.length > 0) { - formattedMetadataBlock.fields[fieldName] = primitiveMultipleFieldValues - return - } - return - } - - if (this.isComposedSingleFieldValue(fieldValue)) { - const formattedMetadataChildFieldValue: DatasetMetadataChildFieldValueDTO = {} - - Object.entries(fieldValue).forEach(([nestedFieldName, nestedFieldValue]) => { - if (nestedFieldValue !== '') { - formattedMetadataChildFieldValue[nestedFieldName] = nestedFieldValue - } - }) - if (Object.keys(formattedMetadataChildFieldValue).length > 0) { - formattedMetadataBlock.fields[fieldName] = formattedMetadataChildFieldValue - return - } - return - } - - if (this.isComposedMultipleFieldValue(fieldValue)) { - const formattedMetadataChildFieldValues: DatasetMetadataChildFieldValueDTO[] = [] - - fieldValue.forEach((composedFieldValues) => { - const composedField: DatasetMetadataChildFieldValueDTO = {} - Object.entries(composedFieldValues).forEach(([nestedFieldName, nestedFieldValue]) => { - if (nestedFieldValue !== '') { - composedField[nestedFieldName] = nestedFieldValue - } - }) - if (Object.keys(composedField).length > 0) { - formattedMetadataChildFieldValues.push(composedField) - } - }) - if (formattedMetadataChildFieldValues.length > 0) { - formattedMetadataBlock.fields[fieldName] = formattedMetadataChildFieldValues - } - - return - } - }) - - metadataBlocks.push(formattedMetadataBlock) - } - return { metadataBlocks } - } - - /* - * To define the field name that will be used to register the field in the form - * Most basic could be: metadataBlockName.name eg: citation.title - * If the field is part of a compound field, the name will be: metadataBlockName.compoundParentName.name eg: citation.author.authorName - * If the field is part of an array of fields, the name will be: metadataBlockName.fieldsArrayIndex.name eg: citation.alternativeTitle.0.value - * If the field is part of a compound field that is part of an array of fields, the name will be: metadataBlockName.compoundParentName.fieldsArrayIndex.name eg: citation.author.0.authorName - */ - public static defineFieldName( - name: string, - metadataBlockName: string, - compoundParentName?: string, - fieldsArrayIndex?: number - ) { - if (fieldsArrayIndex !== undefined && !compoundParentName) { - return `${metadataBlockName}.${name}.${fieldsArrayIndex}.value` - } - if (fieldsArrayIndex !== undefined && compoundParentName) { - return `${metadataBlockName}.${compoundParentName}.${fieldsArrayIndex}.${name}` - } - - if (compoundParentName) { - return `${metadataBlockName}.${compoundParentName}.${name}` - } - return `${metadataBlockName}.${name}` - } - - private static isPrimitiveFieldValue = (value: unknown): value is string => { - return typeof value === 'string' - } - private static isPrimitiveMultipleFieldValue = ( - value: unknown - ): value is PrimitiveMultipleFormValue => { - return Array.isArray(value) && value.every((v) => typeof v === 'object' && 'value' in v) - } - private static isVocabularyMultipleFieldValue = ( - value: unknown - ): value is VocabularyMultipleFormValue => { - return Array.isArray(value) && value.every((v) => typeof v === 'string') - } - private static isComposedSingleFieldValue = ( - value: unknown - ): value is ComposedSingleFieldValue => { - return typeof value === 'object' && !Array.isArray(value) - } - private static isComposedMultipleFieldValue = ( - value: unknown - ): value is ComposedSingleFieldValue[] => { - return Array.isArray(value) && value.every((v) => typeof v === 'object') - } -} diff --git a/src/sections/create-dataset/useCreateDatasetForm.tsx b/src/sections/create-dataset/useCreateDatasetForm.tsx deleted file mode 100644 index d8e8d18c8..000000000 --- a/src/sections/create-dataset/useCreateDatasetForm.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { useState } from 'react' -import { useNavigate } from 'react-router-dom' -import { useTranslation } from 'react-i18next' -import { createDataset } from '../../dataset/domain/useCases/createDataset' -import { DatasetRepository } from '../../dataset/domain/repositories/DatasetRepository' -import { type CreateDatasetFormValues, MetadataFieldsHelper } from './MetadataFieldsHelper' -import { getValidationFailedFieldError } from '../../metadata-block-info/domain/models/fieldValidations' -import { Route } from '../Route.enum' - -export enum SubmissionStatus { - NotSubmitted = 'NotSubmitted', - IsSubmitting = 'IsSubmitting', - SubmitComplete = 'SubmitComplete', - Errored = 'Errored' -} - -type UseCreateDatasetFormReturnType = - | { - submissionStatus: - | SubmissionStatus.NotSubmitted - | SubmissionStatus.IsSubmitting - | SubmissionStatus.SubmitComplete - submitForm: (formData: CreateDatasetFormValues) => void - createError: null - } - | { - submissionStatus: SubmissionStatus.Errored - submitForm: (formData: CreateDatasetFormValues) => void - createError: string - } - -export function useCreateDatasetForm( - repository: DatasetRepository, - collectionId: string, - onCreateErrorCallback: () => void -): UseCreateDatasetFormReturnType { - const [submissionStatus, setSubmissionStatus] = useState( - SubmissionStatus.NotSubmitted - ) - const [createError, setCreateError] = useState(null) - - const navigate = useNavigate() - - const { t } = useTranslation('createDataset') - - const submitForm = (formData: CreateDatasetFormValues): void => { - setSubmissionStatus(SubmissionStatus.IsSubmitting) - - const formDataBackToOriginalKeys = MetadataFieldsHelper.replaceSlashKeysWithDot(formData) - - const formattedFormValues = MetadataFieldsHelper.formatFormValuesToCreateDatasetDTO( - formDataBackToOriginalKeys - ) - - createDataset(repository, formattedFormValues, collectionId) - .then(({ persistentId }) => { - setCreateError(null) - setSubmissionStatus(SubmissionStatus.SubmitComplete) - navigate(`${Route.DATASETS}?persistentId=${persistentId}`, { - state: { created: true } - }) - return - }) - .catch((err) => { - const errorMessage = - err instanceof Error && err.message - ? getValidationFailedFieldError(err.message) ?? err.message - : t('validationAlert.content') - - setCreateError(errorMessage) - setSubmissionStatus(SubmissionStatus.Errored) - - onCreateErrorCallback() - }) - } - - return { - submissionStatus, - submitForm, - createError - } as UseCreateDatasetFormReturnType -} diff --git a/src/sections/create-dataset/useGetMetadataBlocksInfo.tsx b/src/sections/create-dataset/useGetMetadataBlocksInfo.tsx deleted file mode 100644 index 4ee7eab4f..000000000 --- a/src/sections/create-dataset/useGetMetadataBlocksInfo.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { useEffect, useState } from 'react' -import { getDisplayedOnCreateMetadataBlockInfoByCollectionId } from '../../metadata-block-info/domain/useCases/getDisplayedOnCreateMetadataBlockInfoByCollectionId' -import { MetadataBlockInfoRepository } from '../../metadata-block-info/domain/repositories/MetadataBlockInfoRepository' -import { MetadataBlockInfo } from '../../metadata-block-info/domain/models/MetadataBlockInfo' -import { MetadataFieldsHelper } from './MetadataFieldsHelper' - -interface Props { - metadataBlockInfoRepository: MetadataBlockInfoRepository - collectionId: string -} - -interface UseGetMetadataBlocksInfoReturn { - metadataBlocks: MetadataBlockInfo[] - error: string | null - isLoading: boolean -} - -export const useGetMetadataBlocksInfo = ({ - metadataBlockInfoRepository, - collectionId -}: Props): UseGetMetadataBlocksInfoReturn => { - const [metadataBlocks, setMetadataBlocks] = useState([]) - const [isLoading, setIsLoading] = useState(true) - const [error, setError] = useState(null) - - useEffect(() => { - const handleGetDatasetMetadataBlockFields = async () => { - setIsLoading(true) - try { - const metadataBlocksInfo: MetadataBlockInfo[] = - await getDisplayedOnCreateMetadataBlockInfoByCollectionId( - metadataBlockInfoRepository, - collectionId - ) - - const mappedMetadataBlocks = - MetadataFieldsHelper.replaceDotNamesKeysWithSlash(metadataBlocksInfo) - - setMetadataBlocks(mappedMetadataBlocks) - } catch (err) { - console.error(err) - const errorMessage = - err instanceof Error && err.message - ? err.message - : /* istanbul ignore next */ 'Something went wrong getting the information from the metadata blocks. Try again later.' - setError(errorMessage) - } finally { - setIsLoading(false) - } - } - void handleGetDatasetMetadataBlockFields() - }, [collectionId, metadataBlockInfoRepository]) - - return { - metadataBlocks, - error, - isLoading - } -} diff --git a/src/sections/dataset/Dataset.tsx b/src/sections/dataset/Dataset.tsx index ec4330db0..5069560ca 100644 --- a/src/sections/dataset/Dataset.tsx +++ b/src/sections/dataset/Dataset.tsx @@ -1,3 +1,4 @@ +import { useEffect } from 'react' import { Col, Row, Tabs } from '@iqss/dataverse-design-system' import styles from './Dataset.module.scss' import { DatasetLabels } from './dataset-labels/DatasetLabels' @@ -12,7 +13,6 @@ import { DatasetFiles } from './dataset-files/DatasetFiles' import { FileRepository } from '../../files/domain/repositories/FileRepository' import { DatasetActionButtons } from './dataset-action-buttons/DatasetActionButtons' import { useDataset } from './DatasetContext' -import { useEffect } from 'react' import { DatasetAlerts } from './dataset-alerts/DatasetAlerts' import { useNotImplementedModal } from '../not-implemented/NotImplementedModalContext' import { NotImplementedModal } from '../not-implemented/NotImplementedModal' @@ -25,19 +25,31 @@ import { DatasetFilesScrollable } from './dataset-files/DatasetFilesScrollable' interface DatasetProps { fileRepository: FileRepository created?: boolean + metadataUpdated?: boolean filesTabInfiniteScrollEnabled?: boolean } -export function Dataset({ fileRepository, created, filesTabInfiniteScrollEnabled }: DatasetProps) { +export function Dataset({ + fileRepository, + created, + metadataUpdated, + filesTabInfiniteScrollEnabled +}: DatasetProps) { const { setIsLoading } = useLoading() const { dataset, isLoading } = useDataset() const { t } = useTranslation('dataset') const { hideModal, isModalOpen } = useNotImplementedModal() const { addDatasetAlert } = useAlertContext() - if (created) { - addDatasetAlert({ messageKey: AlertMessageKey.DATASET_CREATED, variant: 'success' }) - } + useEffect(() => { + if (metadataUpdated) { + addDatasetAlert({ messageKey: AlertMessageKey.METADATA_UPDATED, variant: 'success' }) + } + if (created) { + addDatasetAlert({ messageKey: AlertMessageKey.DATASET_CREATED, variant: 'success' }) + } + }, [metadataUpdated, created, addDatasetAlert]) + useEffect(() => { setIsLoading(isLoading) }, [isLoading, setIsLoading]) diff --git a/src/sections/dataset/DatasetFactory.tsx b/src/sections/dataset/DatasetFactory.tsx index 6657e9f34..e45b188d7 100644 --- a/src/sections/dataset/DatasetFactory.tsx +++ b/src/sections/dataset/DatasetFactory.tsx @@ -50,8 +50,9 @@ function DatasetWithSearchParams() { const searchParamVersion = searchParams.get('version') ?? undefined const version = searchParamVersionToDomainVersion(searchParamVersion) const location = useLocation() - const state = location.state as { created: boolean } | undefined + const state = location.state as { created: boolean; metadataUpdated: boolean } | undefined const created = state?.created ?? false + const metadataUpdated = state?.metadataUpdated ?? false useEffect(() => { if (privateUrlToken) setAnonymizedView(true) @@ -77,6 +78,7 @@ function DatasetWithSearchParams() { diff --git a/src/sections/dataset/DatasetSkeleton.tsx b/src/sections/dataset/DatasetSkeleton.tsx index 5b7be1cb3..a04bddf2a 100644 --- a/src/sections/dataset/DatasetSkeleton.tsx +++ b/src/sections/dataset/DatasetSkeleton.tsx @@ -1,7 +1,6 @@ import Skeleton, { SkeletonTheme } from 'react-loading-skeleton' import styles from './Dataset.module.scss' import { Row, Col, Tabs } from '@iqss/dataverse-design-system' -import 'react-loading-skeleton/dist/skeleton.css' import { BreadcrumbsSkeleton } from '../shared/hierarchy/BreadcrumbsSkeleton' export function DatasetSkeleton() { diff --git a/src/sections/dataset/dataset-action-buttons/edit-dataset-menu/EditDatasetMenu.tsx b/src/sections/dataset/dataset-action-buttons/edit-dataset-menu/EditDatasetMenu.tsx index 7df59e0f1..bdee13419 100644 --- a/src/sections/dataset/dataset-action-buttons/edit-dataset-menu/EditDatasetMenu.tsx +++ b/src/sections/dataset/dataset-action-buttons/edit-dataset-menu/EditDatasetMenu.tsx @@ -33,6 +33,10 @@ export function EditDatasetMenu({ dataset }: EditDatasetMenuProps) { navigate(`${Route.UPLOAD_DATASET_FILES}?persistentId=${dataset.persistentId}`) return } + if (eventKey === EditDatasetMenuItems.METADATA) { + navigate(`${Route.EDIT_DATASET_METADATA}?persistentId=${dataset.persistentId}`) + return + } showModal() } diff --git a/src/sections/dataset/dataset-files/files-table/FilesTableBody.tsx b/src/sections/dataset/dataset-files/files-table/FilesTableBody.tsx index 1bdc4de43..ff55d881b 100644 --- a/src/sections/dataset/dataset-files/files-table/FilesTableBody.tsx +++ b/src/sections/dataset/dataset-files/files-table/FilesTableBody.tsx @@ -5,7 +5,6 @@ import { useTranslation } from 'react-i18next' import styles from './FilesTable.module.scss' import { getCellStyle } from './FilesTable' import { SentryRef } from '../DatasetFilesScrollable' -import 'react-loading-skeleton/dist/skeleton.css' type FilesTableBodyProps = | { diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.tsx index c829c5345..8353af38f 100644 --- a/src/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.tsx @@ -49,13 +49,17 @@ export function DownloadFilesButton({ files, fileSelection }: DownloadFilesButto return <> } + const dropdownButtonTitle = isBelow768px + ? '' + : /* istanbul ignore next */ t('actions.downloadFiles.title') + if (dataset.hasOneTabularFileAtLeast) { return ( <> } - title={isBelow768px ? '' : t('actions.downloadFiles.title')} + title={dropdownButtonTitle} ariaLabel={t('actions.downloadFiles.title')} variant="secondary" withSpacing> @@ -84,7 +88,7 @@ export function DownloadFilesButton({ files, fileSelection }: DownloadFilesButto aria-label={t('actions.downloadFiles.title')} withSpacing onClick={onClick}> - {isBelow768px ? '' : t('actions.downloadFiles.title')} + {dropdownButtonTitle} { + const { t } = useTranslation('editDatasetMetadata') + const { dataset, isLoading } = useDataset() + const { setIsLoading } = useLoading() + + useEffect(() => { + setIsLoading(isLoading) + }, [isLoading, setIsLoading]) + + const datasetParentCollection: UpwardHierarchyNode = useMemo( + () => + dataset?.hierarchy + .toArray() + .filter((item) => item.type === 'collection') + .at(-1) as UpwardHierarchyNode, + [dataset] + ) + + if (isLoading) { + return + } + + return ( + <> + {!dataset ? ( + + ) : ( + <> + + + {t('infoAlert.text')} + + + + + +
+ +
+
+
+ + )} + + ) +} diff --git a/src/sections/edit-dataset-metadata/EditDatasetMetadataFactory.tsx b/src/sections/edit-dataset-metadata/EditDatasetMetadataFactory.tsx new file mode 100644 index 000000000..82a90b8d1 --- /dev/null +++ b/src/sections/edit-dataset-metadata/EditDatasetMetadataFactory.tsx @@ -0,0 +1,29 @@ +import { ReactElement } from 'react' +import { useSearchParams } from 'react-router-dom' +import { EditDatasetMetadata } from './EditDatasetMetadata' +import { DatasetProvider } from '../dataset/DatasetProvider' +import { DatasetJSDataverseRepository } from '../../dataset/infrastructure/repositories/DatasetJSDataverseRepository' +import { MetadataBlockInfoJSDataverseRepository } from '../../metadata-block-info/infrastructure/repositories/MetadataBlockInfoJSDataverseRepository' + +const datasetRepository = new DatasetJSDataverseRepository() +const metadataBlockInfoRepository = new MetadataBlockInfoJSDataverseRepository() + +export class EditDatasetMetadataFactory { + static create(): ReactElement { + return + } +} + +function EditDatasetMetadataWithParams() { + const [searchParams] = useSearchParams() + const persistentId = searchParams.get('persistentId') ?? undefined + + return ( + + + + ) +} diff --git a/src/sections/edit-dataset-metadata/EditDatasetMetadataSkeleton.tsx b/src/sections/edit-dataset-metadata/EditDatasetMetadataSkeleton.tsx new file mode 100644 index 000000000..48311e579 --- /dev/null +++ b/src/sections/edit-dataset-metadata/EditDatasetMetadataSkeleton.tsx @@ -0,0 +1,32 @@ +import { useTranslation } from 'react-i18next' +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton' +import { Row, Col, Tabs } from '@iqss/dataverse-design-system' +import { BreadcrumbsSkeleton } from '../shared/hierarchy/BreadcrumbsSkeleton' +import { SeparationLine } from '../shared/layout/SeparationLine/SeparationLine' + +export function EditDatasetMetadataSkeleton() { + const { t } = useTranslation('editDatasetMetadata') + + return ( + +
+ + + + + + + + + + + + + + + + +
+
+ ) +} diff --git a/src/sections/edit-dataset-metadata/HostCollection.tsx b/src/sections/edit-dataset-metadata/HostCollection.tsx new file mode 100644 index 000000000..eb9a6aade --- /dev/null +++ b/src/sections/edit-dataset-metadata/HostCollection.tsx @@ -0,0 +1,21 @@ +import { useTranslation } from 'react-i18next' +import { Col, Form } from '@iqss/dataverse-design-system' + +interface HostCollectionProps { + collectionName: string +} + +export const HostCollection = ({ collectionName }: HostCollectionProps) => { + const { t } = useTranslation('editDatasetMetadata') + + return ( + + + {t('hostCollection.label')} + + + + + + ) +} diff --git a/src/sections/file/file-citation/FileCitation.tsx b/src/sections/file/file-citation/FileCitation.tsx index 6beafed84..15a923cef 100644 --- a/src/sections/file/file-citation/FileCitation.tsx +++ b/src/sections/file/file-citation/FileCitation.tsx @@ -1,9 +1,9 @@ -import { DatasetPublishingStatus, DatasetVersion } from '../../../dataset/domain/models/Dataset' -import styles from './FileCitation.module.scss' +import { Col, Row } from '@iqss/dataverse-design-system' import { CitationDescription } from '../../shared/citation/CitationDescription' +import { DatasetPublishingStatus, DatasetVersion } from '../../../dataset/domain/models/Dataset' import { DatasetCitationTooltip } from '../../dataset/dataset-citation/DatasetCitationTooltip' import { CitationLearnAbout } from '../../shared/citation/CitationLearnAbout' -import { Col, Row } from '@iqss/dataverse-design-system' +import styles from './FileCitation.module.scss' interface FileCitationProps { citation: string diff --git a/src/sections/file/file-labels/FileLabels.tsx b/src/sections/file/file-labels/FileLabels.tsx index a6eb67634..403671774 100644 --- a/src/sections/file/file-labels/FileLabels.tsx +++ b/src/sections/file/file-labels/FileLabels.tsx @@ -1,5 +1,5 @@ -import { FileLabel, FileLabelType } from '../../../files/domain/models/FileMetadata' import { Badge } from '@iqss/dataverse-design-system' +import { FileLabel, FileLabelType } from '../../../files/domain/models/FileMetadata' import styles from './FileLabels.module.scss' const VARIANT_BY_LABEL_TYPE: Record = { diff --git a/src/sections/shared/add-data-actions/AddDataActionsButton.tsx b/src/sections/shared/add-data-actions/AddDataActionsButton.tsx index 8c7377804..701651170 100644 --- a/src/sections/shared/add-data-actions/AddDataActionsButton.tsx +++ b/src/sections/shared/add-data-actions/AddDataActionsButton.tsx @@ -1,15 +1,17 @@ import { useTranslation } from 'react-i18next' import { Dropdown } from 'react-bootstrap' -import { Link, useSearchParams } from 'react-router-dom' +import { Link } from 'react-router-dom' import { DropdownButton } from '@iqss/dataverse-design-system' import { PlusLg } from 'react-bootstrap-icons' import { Route } from '../../Route.enum' import styles from './AddDataActionsButton.module.scss' -export default function AddDataActionsButton() { +interface AddDataActionsButtonProps { + collectionId?: string +} + +export default function AddDataActionsButton({ collectionId }: AddDataActionsButtonProps) { const { t } = useTranslation('header') - const [searchParams] = useSearchParams() - const collectionId = searchParams.get('id') ?? undefined const createDatasetRoute = collectionId ? `${Route.CREATE_DATASET}?collectionId=${collectionId}` @@ -32,4 +34,3 @@ export default function AddDataActionsButton() { } // TODO: AddData Dropdown item needs proper permissions checking, see Spike #318 -// TODO: Add page for "New Collection", see Issue #319 diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataFieldsHelper.ts b/src/sections/shared/form/DatasetMetadataForm/MetadataFieldsHelper.ts new file mode 100644 index 000000000..ff51b32a0 --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataFieldsHelper.ts @@ -0,0 +1,464 @@ +import { + MetadataBlockInfo, + MetadataBlockInfoWithMaybeValues, + MetadataField, + MetadataFieldWithMaybeValue +} from '../../../../metadata-block-info/domain/models/MetadataBlockInfo' +import { + DatasetDTO, + DatasetMetadataBlockValuesDTO, + DatasetMetadataChildFieldValueDTO +} from '../../../../dataset/domain/useCases/DTOs/DatasetDTO' +import { + DatasetMetadataBlocks, + DatasetMetadataFields, + DatasetMetadataSubField +} from '../../../../dataset/domain/models/Dataset' + +export type DatasetMetadataFormValues = Record + +export type MetadataBlockFormValues = Record< + string, + string | PrimitiveMultipleFormValue | VocabularyMultipleFormValue | ComposedFieldValues +> + +type VocabularyMultipleFormValue = string[] + +type PrimitiveMultipleFormValue = { value: string }[] + +type ComposedFieldValues = ComposedSingleFieldValue | ComposedSingleFieldValue[] + +type ComposedSingleFieldValue = Record + +export class MetadataFieldsHelper { + public static replaceMetadataBlocksInfoDotNamesKeysWithSlash( + metadataBlocks: MetadataBlockInfo[] + ): MetadataBlockInfo[] { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const metadataBlocksCopy: MetadataBlockInfo[] = structuredClone(metadataBlocks) + + for (const block of metadataBlocksCopy) { + if (block.metadataFields) { + this.metadataBlocksInfoDotReplacer(block.metadataFields) + } + } + return metadataBlocksCopy + } + + private static metadataBlocksInfoDotReplacer(metadataFields: Record) { + for (const key in metadataFields) { + const field = metadataFields[key] + if (field.name.includes('.')) { + field.name = this.replaceDotWithSlash(field.name) + } + if (field.childMetadataFields) { + this.metadataBlocksInfoDotReplacer(field.childMetadataFields) + } + } + } + + public static replaceDatasetMetadataBlocksCurrentValuesDotKeysWithSlash( + datasetMetadataBlocks: DatasetMetadataBlocks + ): DatasetMetadataBlocks { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const datasetMetadataBlocksCopy: DatasetMetadataBlocks = structuredClone(datasetMetadataBlocks) + const dataWithoutKeysWithDots: DatasetMetadataBlocks = [] as unknown as DatasetMetadataBlocks + + for (const block of datasetMetadataBlocksCopy) { + const newBlockFields: DatasetMetadataFields = + this.datasetMetadataBlocksCurrentValuesDotReplacer(block.fields) + + const newBlock = { + name: block.name, + fields: newBlockFields + } + + dataWithoutKeysWithDots.push(newBlock) + } + + return dataWithoutKeysWithDots + } + + private static datasetMetadataBlocksCurrentValuesDotReplacer( + datasetMetadataFields: DatasetMetadataFields + ): DatasetMetadataFields { + const datasetMetadataFieldsNormalized: DatasetMetadataFields = {} + + for (const key in datasetMetadataFields) { + const newKey = key.includes('.') ? this.replaceDotWithSlash(key) : key + + const value = datasetMetadataFields[key] + + // Case of DatasetMetadataSubField + if (typeof value === 'object' && !Array.isArray(value)) { + const nestedKeysMapped = Object.entries(value).reduce((acc, [nestedKey, nestedValue]) => { + const newNestedKey = nestedKey.includes('.') + ? this.replaceDotWithSlash(nestedKey) + : nestedKey + + acc[newNestedKey] = nestedValue + return acc + }, {} as DatasetMetadataSubField) + + datasetMetadataFieldsNormalized[newKey] = nestedKeysMapped + } else if ( + Array.isArray(value) && + (value as readonly (string | DatasetMetadataSubField)[]).every((v) => typeof v === 'object') + ) { + // Case of DatasetMetadataSubField[] + const nestedKeysMapped = value.map((subFields) => { + return Object.entries(subFields).reduce((acc, [nestedKey, nestedValue]) => { + const newNestedKey = nestedKey.includes('.') + ? this.replaceDotWithSlash(nestedKey) + : nestedKey + + acc[newNestedKey] = nestedValue + return acc + }, {} as DatasetMetadataSubField) + }) + datasetMetadataFieldsNormalized[newKey] = nestedKeysMapped + } else { + datasetMetadataFieldsNormalized[newKey] = value + } + } + + return datasetMetadataFieldsNormalized + } + + public static getFormDefaultValues( + metadataBlocks: MetadataBlockInfoWithMaybeValues[] + ): DatasetMetadataFormValues { + const formDefaultValues: DatasetMetadataFormValues = {} + + for (const block of metadataBlocks) { + const blockValues: MetadataBlockFormValues = {} + + for (const field of Object.values(block.metadataFields)) { + const fieldName = field.name + const fieldValue = field.value + + if (field.typeClass === 'compound') { + const childFieldsWithEmptyValues: Record = {} + + if (field.childMetadataFields) { + for (const childField of Object.values(field.childMetadataFields)) { + if (childField.typeClass === 'primitive') { + childFieldsWithEmptyValues[childField.name] = '' + } + + if (childField.typeClass === 'controlledVocabulary') { + childFieldsWithEmptyValues[childField.name] = '' + } + } + } + + if (fieldValue) { + const castedFieldValue = fieldValue as + | DatasetMetadataSubField + | DatasetMetadataSubField[] + + let fieldValues: ComposedFieldValues + + if (Array.isArray(castedFieldValue)) { + const subFieldsWithValuesPlusEmptyOnes = castedFieldValue.map((subFields) => { + const fieldsValueNormalized: Record = Object.entries( + subFields + ).reduce((acc, [key, value]) => { + if (value !== undefined) { + acc[key] = value + } + return acc + }, {} as Record) + + return { + ...childFieldsWithEmptyValues, + ...fieldsValueNormalized + } + }) + + fieldValues = subFieldsWithValuesPlusEmptyOnes + } else { + const fieldsValueNormalized: Record = Object.entries( + castedFieldValue + ).reduce((acc, [key, value]) => { + if (value !== undefined) { + acc[key] = value + } + return acc + }, {} as Record) + + fieldValues = { + ...childFieldsWithEmptyValues, + ...fieldsValueNormalized + } + } + + blockValues[fieldName] = fieldValues + } else { + blockValues[fieldName] = field.multiple + ? [childFieldsWithEmptyValues] + : childFieldsWithEmptyValues + } + } + + if (field.typeClass === 'primitive') { + blockValues[fieldName] = this.getPrimitiveFieldDefaultFormValue(field) + } + + if (field.typeClass === 'controlledVocabulary') { + blockValues[fieldName] = this.getControlledVocabFieldDefaultFormValue(field) + } + } + + formDefaultValues[block.name] = blockValues + } + + return formDefaultValues + } + + private static getPrimitiveFieldDefaultFormValue( + field: MetadataFieldWithMaybeValue + ): string | PrimitiveMultipleFormValue { + if (field.multiple) { + const castedFieldValue = field.value as string[] | undefined + + if (!castedFieldValue) return [{ value: '' }] + + return castedFieldValue.map((stringValue) => ({ value: stringValue })) + } + const castedFieldValue = field.value as string | undefined + return castedFieldValue ?? '' + } + + private static getControlledVocabFieldDefaultFormValue( + field: MetadataFieldWithMaybeValue + ): string | VocabularyMultipleFormValue { + if (field.multiple) { + const castedFieldValue = field.value as string[] | undefined + + if (!castedFieldValue) return [] + + return castedFieldValue + } + const castedFieldValue = field.value as string | undefined + return castedFieldValue ?? '' + } + + public static replaceSlashKeysWithDot(obj: DatasetMetadataFormValues): DatasetMetadataFormValues { + const formattedNewObject: DatasetMetadataFormValues = {} + + for (const key in obj) { + const blockKey = this.replaceSlashWithDot(key) + const metadataBlockFormValues = obj[key] + + formattedNewObject[blockKey] = {} + + Object.entries(metadataBlockFormValues).forEach(([fieldName, fieldValue]) => { + const newFieldName = this.replaceSlashWithDot(fieldName) + + if ( + this.isPrimitiveFieldValue(fieldValue) || + this.isVocabularyMultipleFieldValue(fieldValue) || + this.isPrimitiveMultipleFieldValue(fieldValue) + ) { + formattedNewObject[blockKey][newFieldName] = fieldValue + return + } + + if (this.isComposedSingleFieldValue(fieldValue)) { + formattedNewObject[blockKey][newFieldName] = {} + Object.entries(fieldValue).forEach(([nestedFieldName, nestedFieldValue]) => { + const newNestedFieldName = this.replaceSlashWithDot(nestedFieldName) + const parentOfNestedField = formattedNewObject[blockKey][ + newFieldName + ] as ComposedSingleFieldValue + + parentOfNestedField[newNestedFieldName] = nestedFieldValue + }) + return + } + + if (this.isComposedMultipleFieldValue(fieldValue)) { + formattedNewObject[blockKey][newFieldName] = fieldValue.map((composedFieldValues) => { + const composedField: ComposedSingleFieldValue = {} + + Object.entries(composedFieldValues).forEach(([nestedFieldName, nestedFieldValue]) => { + const newNestedFieldName = this.replaceSlashWithDot(nestedFieldName) + + composedField[newNestedFieldName] = nestedFieldValue + }) + + return composedField + }) + } + }) + } + + return formattedNewObject + } + + public static formatFormValuesToDatasetDTO(formValues: DatasetMetadataFormValues): DatasetDTO { + const metadataBlocks: DatasetDTO['metadataBlocks'] = [] + + for (const metadataBlockName in formValues) { + const formattedMetadataBlock: DatasetMetadataBlockValuesDTO = { + name: metadataBlockName, + fields: {} + } + const metadataBlockFormValues = formValues[metadataBlockName] + + Object.entries(metadataBlockFormValues).forEach(([fieldName, fieldValue]) => { + if (this.isPrimitiveFieldValue(fieldValue)) { + if (fieldValue !== '') { + formattedMetadataBlock.fields[fieldName] = fieldValue + return + } + return + } + if (this.isVocabularyMultipleFieldValue(fieldValue)) { + if (fieldValue.length > 0) { + formattedMetadataBlock.fields[fieldName] = fieldValue + return + } + return + } + + if (this.isPrimitiveMultipleFieldValue(fieldValue)) { + const primitiveMultipleFieldValues = fieldValue + .map((primitiveField) => primitiveField.value) + .filter((v) => v !== '') + + if (primitiveMultipleFieldValues.length > 0) { + formattedMetadataBlock.fields[fieldName] = primitiveMultipleFieldValues + return + } + return + } + + if (this.isComposedSingleFieldValue(fieldValue)) { + const formattedMetadataChildFieldValue: DatasetMetadataChildFieldValueDTO = {} + + Object.entries(fieldValue).forEach(([nestedFieldName, nestedFieldValue]) => { + if (nestedFieldValue !== '') { + formattedMetadataChildFieldValue[nestedFieldName] = nestedFieldValue + } + }) + if (Object.keys(formattedMetadataChildFieldValue).length > 0) { + formattedMetadataBlock.fields[fieldName] = formattedMetadataChildFieldValue + return + } + return + } + + if (this.isComposedMultipleFieldValue(fieldValue)) { + const formattedMetadataChildFieldValues: DatasetMetadataChildFieldValueDTO[] = [] + + fieldValue.forEach((composedFieldValues) => { + const composedField: DatasetMetadataChildFieldValueDTO = {} + Object.entries(composedFieldValues).forEach(([nestedFieldName, nestedFieldValue]) => { + if (nestedFieldValue !== '') { + composedField[nestedFieldName] = nestedFieldValue + } + }) + if (Object.keys(composedField).length > 0) { + formattedMetadataChildFieldValues.push(composedField) + } + }) + if (formattedMetadataChildFieldValues.length > 0) { + formattedMetadataBlock.fields[fieldName] = formattedMetadataChildFieldValues + } + + return + } + }) + + metadataBlocks.push(formattedMetadataBlock) + } + return { metadataBlocks } + } + + public static addFieldValuesToMetadataBlocksInfo( + normalizedMetadataBlocksInfo: MetadataBlockInfo[], + normalizedDatasetMetadaBlocksCurrentValues: DatasetMetadataBlocks + ): MetadataBlockInfoWithMaybeValues[] { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const normalizedMetadataBlocksInfoCopy: MetadataBlockInfoWithMaybeValues[] = structuredClone( + normalizedMetadataBlocksInfo + ) + + const normalizedCurrentValuesMap: Record = + normalizedDatasetMetadaBlocksCurrentValues.reduce((map, block) => { + map[block.name] = block.fields + return map + }, {} as Record) + + normalizedMetadataBlocksInfoCopy.forEach((block) => { + const currentBlockValues = normalizedCurrentValuesMap[block.name] + + if (currentBlockValues) { + Object.keys(block.metadataFields).forEach((fieldName) => { + const field = block.metadataFields[fieldName] + + if (this.replaceDotWithSlash(fieldName) in currentBlockValues) { + field.value = currentBlockValues[this.replaceDotWithSlash(fieldName)] + } + }) + } + }) + + return normalizedMetadataBlocksInfoCopy + } + + private static replaceDotWithSlash = (str: string) => str.replace(/\./g, '/') + private static replaceSlashWithDot = (str: string) => str.replace(/\//g, '.') + + /* + * To define the field name that will be used to register the field in the form + * Most basic could be: metadataBlockName.name eg: citation.title + * If the field is part of a compound field, the name will be: metadataBlockName.compoundParentName.name eg: citation.author.authorName + * If the field is part of an array of fields, the name will be: metadataBlockName.fieldsArrayIndex.name.value eg: citation.alternativeTitle.0.value + * If the field is part of a compound field that is part of an array of fields, the name will be: metadataBlockName.compoundParentName.fieldsArrayIndex.name eg: citation.author.0.authorName + */ + public static defineFieldName( + name: string, + metadataBlockName: string, + compoundParentName?: string, + fieldsArrayIndex?: number + ) { + if (fieldsArrayIndex !== undefined && !compoundParentName) { + return `${metadataBlockName}.${name}.${fieldsArrayIndex}.value` + } + if (fieldsArrayIndex !== undefined && compoundParentName) { + return `${metadataBlockName}.${compoundParentName}.${fieldsArrayIndex}.${name}` + } + + if (compoundParentName) { + return `${metadataBlockName}.${compoundParentName}.${name}` + } + return `${metadataBlockName}.${name}` + } + + private static isPrimitiveFieldValue = (value: unknown): value is string => { + return typeof value === 'string' + } + private static isPrimitiveMultipleFieldValue = ( + value: unknown + ): value is PrimitiveMultipleFormValue => { + return Array.isArray(value) && value.every((v) => typeof v === 'object' && 'value' in v) + } + private static isVocabularyMultipleFieldValue = ( + value: unknown + ): value is VocabularyMultipleFormValue => { + return Array.isArray(value) && value.every((v) => typeof v === 'string') + } + private static isComposedSingleFieldValue = ( + value: unknown + ): value is ComposedSingleFieldValue => { + return typeof value === 'object' && !Array.isArray(value) + } + private static isComposedMultipleFieldValue = ( + value: unknown + ): value is ComposedSingleFieldValue[] => { + return Array.isArray(value) && value.every((v) => typeof v === 'object') + } +} diff --git a/src/sections/create-dataset/MetadataBlockFormFields/MetadataFormField/Fields/ComposeFieldMultiple.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/ComposeFieldMultiple.tsx similarity index 55% rename from src/sections/create-dataset/MetadataBlockFormFields/MetadataFormField/Fields/ComposeFieldMultiple.tsx rename to src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/ComposeFieldMultiple.tsx index 3adeee59d..babc6d8a4 100644 --- a/src/sections/create-dataset/MetadataBlockFormFields/MetadataFormField/Fields/ComposeFieldMultiple.tsx +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/ComposeFieldMultiple.tsx @@ -1,8 +1,10 @@ +import { useMemo } from 'react' import { useFieldArray, useFormContext } from 'react-hook-form' -import { Col, Form, Row } from '@iqss/dataverse-design-system' -import { type MetadataField } from '../../../../../metadata-block-info/domain/models/MetadataBlockInfo' -import { DynamicFieldsButtons } from '../../../dynamic-fields-buttons/DynamicFieldsButtons' +import { useTranslation } from 'react-i18next' +import { type MetadataField } from '../../../../../../../../metadata-block-info/domain/models/MetadataBlockInfo' import { MetadataFormField, type CommonFieldProps } from '..' +import { Col, Form, Row } from '@iqss/dataverse-design-system' +import { DynamicFieldsButtons } from '../../dynamic-fields-buttons/DynamicFieldsButtons' import cn from 'classnames' import styles from '../index.module.scss' @@ -11,6 +13,7 @@ interface ComposedFieldMultipleProps extends CommonFieldProps { childMetadataFields: Record compoundParentName?: string fieldsArrayIndex?: number + notRequiredWithChildFieldsRequired: boolean } export const ComposedFieldMultiple = ({ @@ -18,10 +21,12 @@ export const ComposedFieldMultiple = ({ title, name, description, - isRequired, - childMetadataFields + childMetadataFields, + rulesToApply, + notRequiredWithChildFieldsRequired }: ComposedFieldMultipleProps) => { const { control } = useFormContext() + const { t } = useTranslation('datasetMetadataForm') const { fields: fieldsArray, @@ -32,6 +37,26 @@ export const ComposedFieldMultiple = ({ control: control }) + const childFieldNamesThatMayBecomeRequired: string[] = useMemo( + () => + notRequiredWithChildFieldsRequired + ? Object.entries(childMetadataFields) + .filter(([_, metadataField]) => metadataField.isRequired) + .map(([key, _]) => key) + : [], + [childMetadataFields, notRequiredWithChildFieldsRequired] + ) + + const childFieldNamesThatTriggerRequired = useMemo( + () => + notRequiredWithChildFieldsRequired + ? Object.entries(childMetadataFields) + .filter(([_, metadataField]) => !metadataField.isRequired) + .map(([key, _]) => key) + : [], + [childMetadataFields, notRequiredWithChildFieldsRequired] + ) + const handleOnAddField = (index: number) => { const firstChildFieldName = Object.values(childMetadataFields)[0].name @@ -49,21 +74,36 @@ export const ComposedFieldMultiple = ({ const handleOnRemoveField = (index: number) => remove(index) return ( - + + {notRequiredWithChildFieldsRequired && ( + + {t('mayBecomeRequired')} + + )} {fieldsArray.map((field, index) => { return ( - + {Object.entries(childMetadataFields).map( ([childMetadataFieldKey, childMetadataFieldInfo]) => { + const isFieldThatMayBecomeRequired = notRequiredWithChildFieldsRequired + ? childFieldNamesThatMayBecomeRequired.includes(childMetadataFieldKey) + : false + return ( ) } diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/ComposedField.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/ComposedField.tsx new file mode 100644 index 000000000..bdb919ea5 --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/ComposedField.tsx @@ -0,0 +1,83 @@ +import { useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import { Col, Form, Row } from '@iqss/dataverse-design-system' +import { type MetadataField } from '../../../../../../../../metadata-block-info/domain/models/MetadataBlockInfo' +import { MetadataFormField, type CommonFieldProps } from '..' +import styles from '../index.module.scss' + +interface ComposedFieldProps extends CommonFieldProps { + metadataBlockName: string + childMetadataFields: Record + compoundParentName?: string + fieldsArrayIndex?: number + notRequiredWithChildFieldsRequired: boolean +} + +export const ComposedField = ({ + metadataBlockName, + title, + name, + description, + childMetadataFields, + rulesToApply, + notRequiredWithChildFieldsRequired +}: ComposedFieldProps) => { + const { t } = useTranslation('datasetMetadataForm') + + const childFieldNamesThatMayBecomeRequired: string[] = useMemo( + () => + notRequiredWithChildFieldsRequired + ? Object.entries(childMetadataFields) + .filter(([_, metadataField]) => metadataField.isRequired) + .map(([key, _]) => key) + : [], + [childMetadataFields, notRequiredWithChildFieldsRequired] + ) + + const childFieldNamesThatTriggerRequired = useMemo( + () => + notRequiredWithChildFieldsRequired + ? Object.entries(childMetadataFields) + .filter(([_, metadataField]) => !metadataField.isRequired) + .map(([key, _]) => key) + : [], + [childMetadataFields, notRequiredWithChildFieldsRequired] + ) + + return ( + + {notRequiredWithChildFieldsRequired && ( + + {t('mayBecomeRequired')} + + )} + + + {Object.entries(childMetadataFields).map( + ([childMetadataFieldKey, childMetadataFieldInfo]) => { + const isFieldThatMayBecomeRequired = notRequiredWithChildFieldsRequired + ? childFieldNamesThatMayBecomeRequired.includes(childMetadataFieldKey) + : false + + return ( + + ) + } + )} + + + + ) +} diff --git a/src/sections/create-dataset/MetadataBlockFormFields/MetadataFormField/Fields/Primitive.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/Primitive.tsx similarity index 56% rename from src/sections/create-dataset/MetadataBlockFormFields/MetadataFormField/Fields/Primitive.tsx rename to src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/Primitive.tsx index c0d9ef594..095a56224 100644 --- a/src/sections/create-dataset/MetadataBlockFormFields/MetadataFormField/Fields/Primitive.tsx +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/Primitive.tsx @@ -1,28 +1,37 @@ import { useMemo } from 'react' +import { useTranslation } from 'react-i18next' import { Controller, useFormContext } from 'react-hook-form' +import useWatchFieldsThatTriggerRequired from '../useWatchFieldsThatTriggerRequired' import { Col, Form, Row } from '@iqss/dataverse-design-system' -import { TypeMetadataFieldOptions } from '../../../../../metadata-block-info/domain/models/MetadataBlockInfo' +import { MetadataFieldsHelper } from '../../../../MetadataFieldsHelper' +import { TypeMetadataFieldOptions } from '../../../../../../../../metadata-block-info/domain/models/MetadataBlockInfo' import { type CommonFieldProps } from '..' -import { MetadataFieldsHelper } from '../../../MetadataFieldsHelper' +import styles from '../index.module.scss' interface PrimitiveProps extends CommonFieldProps { metadataBlockName: string compoundParentName?: string + withinMultipleFieldsGroup: boolean fieldsArrayIndex?: number + isFieldThatMayBecomeRequired?: boolean + childFieldNamesThatTriggerRequired?: string[] } export const Primitive = ({ name, - compoundParentName, - metadataBlockName, - rulesToApply, - description, + type, title, watermark, - type, - isRequired, + description, + displayName, + rulesToApply, + metadataBlockName, + compoundParentName, withinMultipleFieldsGroup, - fieldsArrayIndex + fieldsArrayIndex, + isFieldThatMayBecomeRequired, + childFieldNamesThatTriggerRequired }: PrimitiveProps) => { + const { t } = useTranslation('datasetMetadataForm') const { control } = useFormContext() const builtFieldName = useMemo( @@ -36,13 +45,38 @@ export const Primitive = ({ [name, metadataBlockName, compoundParentName, fieldsArrayIndex] ) + const builtFieldNamesThatTriggerRequired = childFieldNamesThatTriggerRequired?.map((value) => + MetadataFieldsHelper.defineFieldName( + value, + metadataBlockName, + compoundParentName, + fieldsArrayIndex + ) + ) + + const fieldShouldBecomeRequired = useWatchFieldsThatTriggerRequired({ + fieldsToWatch: builtFieldNamesThatTriggerRequired, + builtFieldName + }) + + const updatedRulesToApply = useMemo(() => { + if (isFieldThatMayBecomeRequired && fieldShouldBecomeRequired) { + return { + ...rulesToApply, + required: t('field.required', { displayName, interpolation: { escapeValue: false } }) + } + } + return rulesToApply + }, [rulesToApply, fieldShouldBecomeRequired, displayName, isFieldThatMayBecomeRequired, t]) + const isTextArea = type === TypeMetadataFieldOptions.Textbox return ( {title} @@ -51,17 +85,19 @@ export const Primitive = ({ ( {isTextArea ? ( ) : ( @@ -72,6 +108,7 @@ export const Primitive = ({ isInvalid={invalid} placeholder={watermark} data-fieldtype={type} + aria-required={Boolean(updatedRulesToApply?.required)} ref={ref} /> )} diff --git a/src/sections/create-dataset/MetadataBlockFormFields/MetadataFormField/Fields/PrimitiveMultiple.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/PrimitiveMultiple.tsx similarity index 83% rename from src/sections/create-dataset/MetadataBlockFormFields/MetadataFormField/Fields/PrimitiveMultiple.tsx rename to src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/PrimitiveMultiple.tsx index ab0b64242..200023e79 100644 --- a/src/sections/create-dataset/MetadataBlockFormFields/MetadataFormField/Fields/PrimitiveMultiple.tsx +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/PrimitiveMultiple.tsx @@ -1,29 +1,27 @@ import { useCallback, useMemo } from 'react' import { Controller, useFieldArray, useFormContext } from 'react-hook-form' import { Col, Form, Row } from '@iqss/dataverse-design-system' -import { TypeMetadataFieldOptions } from '../../../../../metadata-block-info/domain/models/MetadataBlockInfo' +import { TypeMetadataFieldOptions } from '../../../../../../../../metadata-block-info/domain/models/MetadataBlockInfo' +import { DynamicFieldsButtons } from '../../dynamic-fields-buttons/DynamicFieldsButtons' +import { MetadataFieldsHelper } from '../../../../MetadataFieldsHelper' import { type CommonFieldProps } from '..' -import { DynamicFieldsButtons } from '../../../dynamic-fields-buttons/DynamicFieldsButtons' -import { MetadataFieldsHelper } from '../../../MetadataFieldsHelper' import cn from 'classnames' import styles from '../index.module.scss' interface PrimitiveMultipleProps extends CommonFieldProps { metadataBlockName: string compoundParentName?: string - fieldsArrayIndex?: number } export const PrimitiveMultiple = ({ name, - compoundParentName, - metadataBlockName, - description, + type, title, + description, watermark, - type, - isRequired, - rulesToApply + rulesToApply, + metadataBlockName, + compoundParentName }: PrimitiveMultipleProps) => { const { control } = useFormContext() @@ -70,8 +68,9 @@ export const PrimitiveMultiple = ({ {title} @@ -83,26 +82,29 @@ export const PrimitiveMultiple = ({ name={builtFieldNameWithIndex(index)} control={control} rules={rulesToApply} - render={({ field: { onChange, ref }, fieldState: { invalid, error } }) => ( + render={({ field: { onChange, ref, value }, fieldState: { invalid, error } }) => ( <> {isTextArea ? ( ) : ( diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/Vocabulary.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/Vocabulary.tsx new file mode 100644 index 000000000..b0b02ad1d --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/Vocabulary.tsx @@ -0,0 +1,111 @@ +import { useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import { Controller, useFormContext } from 'react-hook-form' +import useWatchFieldsThatTriggerRequired from '../useWatchFieldsThatTriggerRequired' +import { Col, Form, Row } from '@iqss/dataverse-design-system' +import { MetadataFieldsHelper } from '../../../../MetadataFieldsHelper' +import { type CommonFieldProps } from '..' +import styles from '../index.module.scss' + +interface VocabularyProps extends CommonFieldProps { + options: string[] + metadataBlockName: string + compoundParentName?: string + withinMultipleFieldsGroup: boolean + fieldsArrayIndex?: number + isFieldThatMayBecomeRequired?: boolean + childFieldNamesThatTriggerRequired?: string[] +} +export const Vocabulary = ({ + name, + title, + displayName, + description, + rulesToApply, + options, + metadataBlockName, + compoundParentName, + withinMultipleFieldsGroup, + fieldsArrayIndex, + isFieldThatMayBecomeRequired, + childFieldNamesThatTriggerRequired +}: VocabularyProps) => { + const { t } = useTranslation('datasetMetadataForm') + + const { control } = useFormContext() + + const builtFieldName = useMemo( + () => + MetadataFieldsHelper.defineFieldName( + name, + metadataBlockName, + compoundParentName, + fieldsArrayIndex + ), + [name, metadataBlockName, compoundParentName, fieldsArrayIndex] + ) + + const builtFieldNamesThatTriggerRequired = childFieldNamesThatTriggerRequired?.map((value) => + MetadataFieldsHelper.defineFieldName( + value, + metadataBlockName, + compoundParentName, + fieldsArrayIndex + ) + ) + + const fieldShouldBecomeRequired = useWatchFieldsThatTriggerRequired({ + fieldsToWatch: builtFieldNamesThatTriggerRequired, + builtFieldName + }) + + const updatedRulesToApply = useMemo(() => { + if (isFieldThatMayBecomeRequired && fieldShouldBecomeRequired) { + return { + ...rulesToApply, + required: t('field.required', { displayName, interpolation: { escapeValue: false } }) + } + } + return rulesToApply + }, [rulesToApply, fieldShouldBecomeRequired, displayName, isFieldThatMayBecomeRequired, t]) + + return ( + ( + + + {title} + + + + + + + {options.map((option) => ( + + ))} + + {error?.message} + + + + + )} + /> + ) +} diff --git a/src/sections/create-dataset/MetadataBlockFormFields/MetadataFormField/Fields/VocabularyMultiple.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/VocabularyMultiple.tsx similarity index 82% rename from src/sections/create-dataset/MetadataBlockFormFields/MetadataFormField/Fields/VocabularyMultiple.tsx rename to src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/VocabularyMultiple.tsx index 5c447658d..6f4c9a51b 100644 --- a/src/sections/create-dataset/MetadataBlockFormFields/MetadataFormField/Fields/VocabularyMultiple.tsx +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/VocabularyMultiple.tsx @@ -1,24 +1,24 @@ import { useMemo } from 'react' import { Controller, useFormContext } from 'react-hook-form' import { Form, Row, Col } from '@iqss/dataverse-design-system' +import { MetadataFieldsHelper } from '../../../../MetadataFieldsHelper' import { type CommonFieldProps } from '..' -import { MetadataFieldsHelper } from '../../../MetadataFieldsHelper' +import styles from '../index.module.scss' interface VocabularyProps extends CommonFieldProps { - metadataBlockName: string options: string[] + metadataBlockName: string compoundParentName?: string fieldsArrayIndex?: number } export const VocabularyMultiple = ({ name, - compoundParentName, - metadataBlockName, - rulesToApply, - description, title, + description, + rulesToApply, options, - isRequired, + metadataBlockName, + compoundParentName, fieldsArrayIndex }: VocabularyProps) => { const { control } = useFormContext() @@ -39,12 +39,13 @@ export const VocabularyMultiple = ({ name={builtFieldName} control={control} rules={rulesToApply} - render={({ field: { onChange, ref }, fieldState: { invalid, error } }) => ( + render={({ field: { onChange, ref, value }, fieldState: { invalid, error } }) => ( {title} @@ -53,6 +54,7 @@ export const VocabularyMultiple = ({ { + compoundParentIsRequired, + isFieldThatMayBecomeRequired, + childFieldNamesThatTriggerRequired +}: DynamicMetadataFormFieldProps) => { const { name, + displayName, type, title, multiple, typeClass, - isRequired, description, watermark, childMetadataFields, - controlledVocabularyValues + controlledVocabularyValues, + isRequired } = metadataFieldInfo - const rulesToApply = useDefineRules({ metadataFieldInfo }) - const isSafeCompound = typeClass === TypeClassMetadataFieldOptions.Compound && childMetadataFields !== undefined && @@ -66,34 +81,46 @@ export const MetadataFormField = ({ const isSafePrimitive = typeClass === TypeClassMetadataFieldOptions.Primitive + const notRequiredWithChildFieldsRequired = + isSafeCompound && + !isRequired && + Object.keys(childMetadataFields).some((key) => childMetadataFields[key].isRequired) + + const rulesToApply = useDefineRules({ + metadataFieldInfo, + isParentFieldRequired: compoundParentIsRequired + }) + if (isSafePrimitive) { if (multiple) { return ( ) } return ( ) } @@ -103,32 +130,32 @@ export const MetadataFormField = ({ return ( ) } return ( ) } @@ -138,16 +165,16 @@ export const MetadataFormField = ({ return ( ) } @@ -155,16 +182,16 @@ export const MetadataFormField = ({ return ( ) } diff --git a/src/sections/create-dataset/MetadataBlockFormFields/MetadataFormField/useDefineRules.ts b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/useDefineRules.ts similarity index 50% rename from src/sections/create-dataset/MetadataBlockFormFields/MetadataFormField/useDefineRules.ts rename to src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/useDefineRules.ts index 23d7367b3..c6050b431 100644 --- a/src/sections/create-dataset/MetadataBlockFormFields/MetadataFormField/useDefineRules.ts +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/useDefineRules.ts @@ -2,29 +2,39 @@ import { UseControllerProps } from 'react-hook-form' import { useTranslation } from 'react-i18next' import { DateFormatsOptions, - MetadataField, + type MetadataField, TypeMetadataFieldOptions -} from '../../../../metadata-block-info/domain/models/MetadataBlockInfo' +} from '../../../../../../../metadata-block-info/domain/models/MetadataBlockInfo' import { - isValidDateFormat, - isValidEmail, + isValidURL, isValidFloat, + isValidEmail, isValidInteger, - isValidURL -} from '../../../../metadata-block-info/domain/models/fieldValidations' + isValidDateFormat +} from '../../../../../../../metadata-block-info/domain/models/fieldValidations' interface Props { metadataFieldInfo: MetadataField + isParentFieldRequired?: boolean } export type DefinedRules = UseControllerProps['rules'] -export const useDefineRules = ({ metadataFieldInfo }: Props): DefinedRules => { - const { t } = useTranslation('createDataset') +export const useDefineRules = ({ + metadataFieldInfo, + isParentFieldRequired +}: Props): DefinedRules => { + const { t } = useTranslation('datasetMetadataForm') const { type, displayName, isRequired, watermark } = metadataFieldInfo + // A sub field is required if the parent field is required and the sub field is required + const isFieldRequired = + isParentFieldRequired !== undefined ? isParentFieldRequired && isRequired : isRequired + const rulesToApply: DefinedRules = { - required: isRequired ? t('datasetForm.field.required', { displayName }) : false, + required: isFieldRequired + ? t('field.required', { displayName, interpolation: { escapeValue: false } }) + : false, validate: (value: string) => { if (!value) { return true @@ -32,7 +42,7 @@ export const useDefineRules = ({ metadataFieldInfo }: Props): DefinedRules => { if (type === TypeMetadataFieldOptions.URL) { if (!isValidURL(value)) { - return t('datasetForm.field.invalid.url', { displayName }) + return t('field.invalid.url', { displayName, interpolation: { escapeValue: false } }) } return true } @@ -41,25 +51,29 @@ export const useDefineRules = ({ metadataFieldInfo }: Props): DefinedRules => { watermark === 'YYYY-MM-DD' ? DateFormatsOptions.YYYYMMDD : undefined if (!isValidDateFormat(value, acceptedDateFormat)) { - return t('datasetForm.field.invalid.date', { displayName, dateFormat: watermark }) + return t('field.invalid.date', { + displayName, + dateFormat: watermark, + interpolation: { escapeValue: false } + }) } return true } if (type === TypeMetadataFieldOptions.Email) { if (!isValidEmail(value)) { - return t('datasetForm.field.invalid.email', { displayName }) + return t('field.invalid.email', { displayName, interpolation: { escapeValue: false } }) } return true } if (type === TypeMetadataFieldOptions.Int) { if (!isValidInteger(value)) { - return t('datasetForm.field.invalid.int', { displayName }) + return t('field.invalid.int', { displayName, interpolation: { escapeValue: false } }) } return true } if (type === TypeMetadataFieldOptions.Float) { if (!isValidFloat(value)) { - return t('datasetForm.field.invalid.float', { displayName }) + return t('field.invalid.float', { displayName, interpolation: { escapeValue: false } }) } return true } diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/useWatchFieldsThatTriggerRequired.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/useWatchFieldsThatTriggerRequired.tsx new file mode 100644 index 000000000..c84f85f82 --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/useWatchFieldsThatTriggerRequired.tsx @@ -0,0 +1,29 @@ +import { useEffect } from 'react' +import { useFormContext, useWatch } from 'react-hook-form' + +interface Props { + fieldsToWatch?: string[] + builtFieldName: string +} + +const useWatchFieldsThatTriggerRequired = ({ fieldsToWatch, builtFieldName }: Props): boolean => { + const dependantFieldsValues = useWatch({ + name: fieldsToWatch || [] + }) + + const { clearErrors } = useFormContext() + + const fieldShouldBecomeRequired = fieldsToWatch + ? dependantFieldsValues.some((value) => value) + : false + + useEffect(() => { + if (!fieldShouldBecomeRequired) { + clearErrors(builtFieldName) + } + }, [fieldShouldBecomeRequired, builtFieldName, clearErrors]) + + return fieldShouldBecomeRequired +} + +export default useWatchFieldsThatTriggerRequired diff --git a/src/sections/create-dataset/dynamic-fields-buttons/DynamicFieldsButtons.module.scss b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/dynamic-fields-buttons/DynamicFieldsButtons.module.scss similarity index 100% rename from src/sections/create-dataset/dynamic-fields-buttons/DynamicFieldsButtons.module.scss rename to src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/dynamic-fields-buttons/DynamicFieldsButtons.module.scss diff --git a/src/sections/create-dataset/dynamic-fields-buttons/DynamicFieldsButtons.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/dynamic-fields-buttons/DynamicFieldsButtons.tsx similarity index 70% rename from src/sections/create-dataset/dynamic-fields-buttons/DynamicFieldsButtons.tsx rename to src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/dynamic-fields-buttons/DynamicFieldsButtons.tsx index 02886d17d..bafa1ca4f 100644 --- a/src/sections/create-dataset/dynamic-fields-buttons/DynamicFieldsButtons.tsx +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/dynamic-fields-buttons/DynamicFieldsButtons.tsx @@ -1,9 +1,8 @@ -import { Button } from '@iqss/dataverse-design-system' -import styles from './DynamicFieldsButtons.module.scss' import { MouseEvent } from 'react' +import { Button, Tooltip } from '@iqss/dataverse-design-system' import { Dash, Plus } from 'react-bootstrap-icons' -import { Tooltip } from '@iqss/dataverse-design-system' import { useTranslation } from 'react-i18next' +import styles from './DynamicFieldsButtons.module.scss' interface AddFieldButtonsProps { fieldName: string @@ -18,27 +17,27 @@ export function DynamicFieldsButtons({ onAddButtonClick, onRemoveButtonClick }: AddFieldButtonsProps) { - const { t } = useTranslation('createDataset') + const { t } = useTranslation('datasetMetadataForm') return (
- + {!originalField && ( - + diff --git a/src/sections/create-dataset/MetadataBlockFormFields/index.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/index.tsx similarity index 81% rename from src/sections/create-dataset/MetadataBlockFormFields/index.tsx rename to src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/index.tsx index e29f3f78c..e91094e32 100644 --- a/src/sections/create-dataset/MetadataBlockFormFields/index.tsx +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/index.tsx @@ -1,4 +1,4 @@ -import { type MetadataBlockInfo } from /* istanbul ignore next */ '../../../metadata-block-info/domain/models/MetadataBlockInfo' +import { type MetadataBlockInfo } from '../../../../../../metadata-block-info/domain/models/MetadataBlockInfo' import { MetadataFormField } from './MetadataFormField' interface Props { diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataFormSkeleton.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataFormSkeleton.tsx new file mode 100644 index 000000000..bae08901e --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataFormSkeleton.tsx @@ -0,0 +1,89 @@ +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton' +import { Accordion, Col, Row } from '@iqss/dataverse-design-system' +import { SeparationLine } from '../../../layout/SeparationLine/SeparationLine' + +export const MetadataFormSkeleton = ({ onEditMode }: { onEditMode: boolean }) => ( + +
+
+ + {onEditMode && ( +
+ + +
+ )} +
+ + + + + + + + {Array.from({ length: 4 }).map((_, index) => ( + + + + + + + + + + {index === 2 && ( + + + + )} + + + + ))} + {Array.from({ length: 3 }).map((_, index) => ( + + + + + + + + + + + + + + + + + + + + + + + + + ))} + + + + + + +
+ + + +
+
+
+) diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/index.module.scss b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/index.module.scss new file mode 100644 index 000000000..03ad686c6 --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/index.module.scss @@ -0,0 +1,15 @@ +.form-container { + scroll-margin-top: 62px; +} + +.top-buttons-container { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 1rem; +} + +.bottom-buttons-container { + display: flex; + justify-content: flex-end; +} diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/index.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/index.tsx new file mode 100644 index 000000000..6c63c3aad --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/index.tsx @@ -0,0 +1,217 @@ +import { MouseEvent, useEffect, useMemo, useRef } from 'react' +import { useNavigate } from 'react-router-dom' +import { useTranslation } from 'react-i18next' +import { FieldErrors, FormProvider, useForm } from 'react-hook-form' +import { useSession } from '../../../../session/SessionContext' +import { Accordion, Alert, Button } from '@iqss/dataverse-design-system' +import { type DatasetRepository } from '../../../../../dataset/domain/repositories/DatasetRepository' +import { type MetadataBlockInfo } from '../../../../../metadata-block-info/domain/models/MetadataBlockInfo' +import { type DatasetMetadataFormValues } from '../MetadataFieldsHelper' +import { type DatasetMetadataFormMode } from '..' +import { SubmissionStatus, useSubmitDataset } from '../useSubmitDataset' +import { MetadataBlockFormFields } from './MetadataBlockFormFields' +import { RequiredFieldText } from '../../RequiredFieldText/RequiredFieldText' +import { SeparationLine } from '../../../layout/SeparationLine/SeparationLine' +import styles from './index.module.scss' + +interface FormProps { + mode: DatasetMetadataFormMode + collectionId: string + formDefaultValues: DatasetMetadataFormValues + metadataBlocksInfo: MetadataBlockInfo[] + errorLoadingMetadataBlocksInfo: string | null + datasetRepository: DatasetRepository + datasetPersistentID?: string +} + +export const MetadataForm = ({ + mode, + collectionId, + formDefaultValues, + metadataBlocksInfo, + errorLoadingMetadataBlocksInfo, + datasetRepository, + datasetPersistentID +}: FormProps) => { + const { user } = useSession() + const navigate = useNavigate() + const { t } = useTranslation('datasetMetadataForm') + + const accordionRef = useRef(null) + const formContainerRef = useRef(null) + + const onCreateMode = mode === 'create' + const onEditMode = mode === 'edit' + const isErrorLoadingMetadataBlocks = Boolean(errorLoadingMetadataBlocksInfo) + + const form = useForm({ mode: 'onChange', defaultValues: formDefaultValues }) + const { setValue, formState } = form + + const { submissionStatus, submitError, submitForm } = useSubmitDataset( + mode, + collectionId, + datasetRepository, + onSubmitDatasetError, + datasetPersistentID + ) + + useEffect(() => { + // Only on create mode, lets prefill specific fields with user data + if (mode === 'create' && user) { + setValue('citation.author.0.authorName', user.displayName) + setValue('citation.datasetContact.0.datasetContactName', user.displayName) + setValue('citation.datasetContact.0.datasetContactEmail', user.email, { + shouldValidate: true + }) + if (user.affiliation) { + setValue('citation.datasetContact.0.datasetContactAffiliation', user.affiliation) + setValue('citation.author.0.authorAffiliation', user.affiliation) + } + } + }, [setValue, user, mode]) + + const handleCancel = (event: MouseEvent) => { + event.preventDefault() + navigate(-1) + } + + const onInvalidSubmit = (errors: FieldErrors) => { + if (!accordionRef.current) return + /* + Get the first metadata block accordion item with an error, and if it's collapsed, open it + Only for the case when accordion is closed, otherwise focus is already handled by react-hook-form + */ + const firstMetadataBlockNameWithError = Object.keys(errors)[0] + + const accordionItemsButtons: HTMLButtonElement[] = Array.from( + accordionRef.current.querySelectorAll('button.accordion-button') + ) + + accordionItemsButtons.forEach((button) => { + const parentItem = button.closest('.accordion-item') + const itemBlockName = parentItem?.id.split('-').pop() + const buttonIsCollapsed = button.classList.contains('collapsed') + + if (itemBlockName === firstMetadataBlockNameWithError && buttonIsCollapsed) { + button.click() + + setTimeout( + /* istanbul ignore next */ () => { + const focusedElement = document.activeElement + focusedElement?.scrollIntoView({ behavior: 'smooth', block: 'center' }) + }, + 800 + ) + } + }) + } + + function onSubmitDatasetError() { + if (formContainerRef.current) { + formContainerRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' }) + } + } + + const disableSubmitButton = useMemo(() => { + return ( + isErrorLoadingMetadataBlocks || + submissionStatus === SubmissionStatus.IsSubmitting || + !formState.isDirty + ) + }, [isErrorLoadingMetadataBlocks, submissionStatus, formState.isDirty]) + + const preventEnterSubmit = (e: React.KeyboardEvent) => { + // When pressing Enter, only submit the form if the user is focused on the submit button itself + if (e.key !== 'Enter') return + + const isButton = e.target instanceof HTMLButtonElement + const isButtonTypeSubmit = isButton ? (e.target as HTMLButtonElement).type === 'submit' : false + + if (!isButton && !isButtonTypeSubmit) e.preventDefault() + } + + return ( +
+ +
+
+ + {onEditMode && ( +
+ + +
+ )} +
+ + {submissionStatus === SubmissionStatus.Errored && ( + + {submitError} + + )} + + {submissionStatus === SubmissionStatus.SubmitComplete && ( + + {t('status.success')} + + )} + + {metadataBlocksInfo.length > 0 && ( + + {metadataBlocksInfo.map((metadataBlock, index) => ( + + {metadataBlock.displayName} + + + + + ))} + + )} + + + + {onCreateMode && ( + + {t('metadataTip.content')} + + )} +
+ + +
+ +
+
+ ) +} diff --git a/src/sections/shared/form/DatasetMetadataForm/index.tsx b/src/sections/shared/form/DatasetMetadataForm/index.tsx new file mode 100644 index 000000000..4f041f319 --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/index.tsx @@ -0,0 +1,117 @@ +import { useEffect, useMemo } from 'react' +import { useLoading } from '../../../loading/LoadingContext' +import { useGetMetadataBlocksInfo } from './useGetMetadataBlocksInfo' +import { DatasetRepository } from '../../../../dataset/domain/repositories/DatasetRepository' +import { MetadataBlockInfoRepository } from '../../../../metadata-block-info/domain/repositories/MetadataBlockInfoRepository' +import { MetadataFieldsHelper } from './MetadataFieldsHelper' +import { MetadataFormSkeleton } from './MetadataForm/MetadataFormSkeleton' +import { MetadataForm } from './MetadataForm' +import { DatasetMetadataBlocks } from '../../../../dataset/domain/models/Dataset' +import { Alert } from '@iqss/dataverse-design-system' + +type DatasetMetadataFormProps = + | { + mode: 'create' + collectionId: string + datasetRepository: DatasetRepository + datasetPersistentID?: never + metadataBlockInfoRepository: MetadataBlockInfoRepository + datasetMetadaBlocksCurrentValues?: never + } + | { + mode: 'edit' + collectionId: string + datasetRepository: DatasetRepository + datasetPersistentID: string + metadataBlockInfoRepository: MetadataBlockInfoRepository + datasetMetadaBlocksCurrentValues: DatasetMetadataBlocks + } + +export type DatasetMetadataFormMode = 'create' | 'edit' + +export const DatasetMetadataForm = ({ + mode, + collectionId, + datasetRepository, + datasetPersistentID, + metadataBlockInfoRepository, + datasetMetadaBlocksCurrentValues +}: DatasetMetadataFormProps) => { + const { setIsLoading } = useLoading() + const onEditMode = mode === 'edit' + + const { + metadataBlocksInfo, + isLoading: isLoadingMetadataBlocksInfo, + error: errorLoadingMetadataBlocksInfo + } = useGetMetadataBlocksInfo({ + mode, + collectionId, + metadataBlockInfoRepository + }) + + // Metadata blocks info with field names that have dots replaced by slashes + const normalizedMetadataBlocksInfo = useMemo(() => { + if (metadataBlocksInfo.length === 0) return [] + + return MetadataFieldsHelper.replaceMetadataBlocksInfoDotNamesKeysWithSlash(metadataBlocksInfo) + }, [metadataBlocksInfo]) + + // Dataset metadata blocks current values properties with dots replaced by slashes to match the metadata blocks info + const normalizedDatasetMetadaBlocksCurrentValues = useMemo(() => { + if (!datasetMetadaBlocksCurrentValues) return undefined + + return MetadataFieldsHelper.replaceDatasetMetadataBlocksCurrentValuesDotKeysWithSlash( + datasetMetadaBlocksCurrentValues + ) + }, [datasetMetadaBlocksCurrentValues]) + + // If we are in edit mode, we need to add the values to the metadata blocks info + const normalizedMetadataBlocksInfoWithValues = useMemo(() => { + if (normalizedMetadataBlocksInfo.length === 0 || !normalizedDatasetMetadaBlocksCurrentValues) { + return null + } + + return onEditMode + ? MetadataFieldsHelper.addFieldValuesToMetadataBlocksInfo( + normalizedMetadataBlocksInfo, + normalizedDatasetMetadaBlocksCurrentValues + ) + : null + }, [normalizedMetadataBlocksInfo, normalizedDatasetMetadaBlocksCurrentValues, onEditMode]) + + // Set the form default values object based on the metadata blocks info + const formDefaultValues = useMemo(() => { + return onEditMode && normalizedMetadataBlocksInfoWithValues !== null + ? MetadataFieldsHelper.getFormDefaultValues(normalizedMetadataBlocksInfoWithValues) + : MetadataFieldsHelper.getFormDefaultValues(normalizedMetadataBlocksInfo) + }, [normalizedMetadataBlocksInfo, normalizedMetadataBlocksInfoWithValues, onEditMode]) + + useEffect(() => { + setIsLoading(isLoadingMetadataBlocksInfo) + }, [isLoadingMetadataBlocksInfo, setIsLoading]) + + if (isLoadingMetadataBlocksInfo || !formDefaultValues) { + return + } + + if (errorLoadingMetadataBlocksInfo) { + return ( + + {errorLoadingMetadataBlocksInfo} + + ) + } + + return ( + + ) +} diff --git a/src/sections/shared/form/DatasetMetadataForm/useGetMetadataBlocksInfo.tsx b/src/sections/shared/form/DatasetMetadataForm/useGetMetadataBlocksInfo.tsx new file mode 100644 index 000000000..27e5ce567 --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/useGetMetadataBlocksInfo.tsx @@ -0,0 +1,67 @@ +import { useEffect, useState } from 'react' +import { getMetadataBlockInfoByCollectionId } from '../../../../metadata-block-info/domain/useCases/getMetadataBlockInfoByCollectionId' +import { getDisplayedOnCreateMetadataBlockInfoByCollectionId } from '../../../../metadata-block-info/domain/useCases/getDisplayedOnCreateMetadataBlockInfoByCollectionId' +import { MetadataBlockInfoRepository } from '../../../../metadata-block-info/domain/repositories/MetadataBlockInfoRepository' +import { MetadataBlockInfo } from '../../../../metadata-block-info/domain/models/MetadataBlockInfo' +import { DatasetMetadataFormMode } from '.' + +interface Props { + mode: DatasetMetadataFormMode + collectionId: string + metadataBlockInfoRepository: MetadataBlockInfoRepository +} + +interface UseGetMetadataBlocksInfoReturn { + metadataBlocksInfo: MetadataBlockInfo[] + error: string | null + isLoading: boolean +} + +export const useGetMetadataBlocksInfo = ({ + mode, + collectionId, + metadataBlockInfoRepository +}: Props): UseGetMetadataBlocksInfoReturn => { + const [metadataBlocksInfo, setMetadataBlocksInfo] = useState([]) + const [isLoading, setIsLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + const handleGetDatasetMetadataBlockFields = async () => { + setIsLoading(true) + try { + let metadataBlocks: MetadataBlockInfo[] = [] + + if (mode === 'edit') { + metadataBlocks = await getMetadataBlockInfoByCollectionId( + metadataBlockInfoRepository, + collectionId + ) + } else { + metadataBlocks = await getDisplayedOnCreateMetadataBlockInfoByCollectionId( + metadataBlockInfoRepository, + collectionId + ) + } + + setMetadataBlocksInfo(metadataBlocks) + } catch (err) { + const errorMessage = + err instanceof Error && err.message + ? err.message + : /* istanbul ignore next */ 'Something went wrong getting the information from the metadata blocks. Try again later.' + setError(errorMessage) + } finally { + setIsLoading(false) + } + } + + void handleGetDatasetMetadataBlockFields() + }, [collectionId, metadataBlockInfoRepository, mode]) + + return { + metadataBlocksInfo, + error, + isLoading + } +} diff --git a/src/sections/shared/form/DatasetMetadataForm/useSubmitDataset.ts b/src/sections/shared/form/DatasetMetadataForm/useSubmitDataset.ts new file mode 100644 index 000000000..992a8805c --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/useSubmitDataset.ts @@ -0,0 +1,115 @@ +import { useState } from 'react' +import { useNavigate } from 'react-router-dom' +import { useTranslation } from 'react-i18next' +import { DatasetRepository } from '../../../../dataset/domain/repositories/DatasetRepository' +import { createDataset } from '../../../../dataset/domain/useCases/createDataset' +import { updateDatasetMetadata } from '../../../../dataset/domain/useCases/updateDatasetMetadata' +import { MetadataFieldsHelper, type DatasetMetadataFormValues } from './MetadataFieldsHelper' +import { getValidationFailedFieldError } from '../../../../metadata-block-info/domain/models/fieldValidations' +import { type DatasetMetadataFormMode } from '.' +import { Route } from '../../../Route.enum' + +export enum SubmissionStatus { + NotSubmitted = 'NotSubmitted', + IsSubmitting = 'IsSubmitting', + SubmitComplete = 'SubmitComplete', + Errored = 'Errored' +} + +type UseSubmitDatasetReturnType = + | { + submissionStatus: + | SubmissionStatus.NotSubmitted + | SubmissionStatus.IsSubmitting + | SubmissionStatus.SubmitComplete + submitForm: (formData: DatasetMetadataFormValues) => void + submitError: null + } + | { + submissionStatus: SubmissionStatus.Errored + submitForm: (formData: DatasetMetadataFormValues) => void + submitError: string + } + +export function useSubmitDataset( + mode: DatasetMetadataFormMode, + collectionId: string, + datasetRepository: DatasetRepository, + onSubmitErrorCallback: () => void, + datasetPersistentID?: string +): UseSubmitDatasetReturnType { + const navigate = useNavigate() + const { t } = useTranslation('datasetMetadataForm') + + const [submissionStatus, setSubmissionStatus] = useState( + SubmissionStatus.NotSubmitted + ) + const [submitError, setSubmitError] = useState(null) + + const submitForm = (formData: DatasetMetadataFormValues): void => { + setSubmissionStatus(SubmissionStatus.IsSubmitting) + + const formDataBackToOriginalKeys = MetadataFieldsHelper.replaceSlashKeysWithDot(formData) + + const formattedFormValues = MetadataFieldsHelper.formatFormValuesToDatasetDTO( + formDataBackToOriginalKeys + ) + + if (mode === 'create') { + createDataset(datasetRepository, formattedFormValues, collectionId) + .then(({ persistentId }) => { + setSubmitError(null) + setSubmissionStatus(SubmissionStatus.SubmitComplete) + navigate(`${Route.DATASETS}?persistentId=${persistentId}`, { + state: { created: true } + }) + return + }) + .catch((err) => { + const errorMessage = + err instanceof Error && err.message + ? getValidationFailedFieldError(err.message) ?? err.message + : t('validationAlert.content') + + setSubmitError(errorMessage) + setSubmissionStatus(SubmissionStatus.Errored) + + onSubmitErrorCallback() + }) + } else { + const currentEditedDatasetPersistentID = datasetPersistentID as string + + updateDatasetMetadata( + datasetRepository, + currentEditedDatasetPersistentID, + formattedFormValues + ) + .then(() => { + setSubmitError(null) + setSubmissionStatus(SubmissionStatus.SubmitComplete) + navigate(`${Route.DATASETS}?persistentId=${currentEditedDatasetPersistentID}`, { + state: { metadataUpdated: true } + }) + + return + }) + .catch((err) => { + const errorMessage = + err instanceof Error && err.message + ? getValidationFailedFieldError(err.message) ?? err.message + : t('validationAlert.content') + + setSubmitError(errorMessage) + setSubmissionStatus(SubmissionStatus.Errored) + + onSubmitErrorCallback() + }) + } + } + + return { + submissionStatus, + submitForm, + submitError + } as UseSubmitDatasetReturnType +} diff --git a/src/sections/shared/form/RequiredFieldText/RequiredFieldText.tsx b/src/sections/shared/form/RequiredFieldText/RequiredFieldText.tsx index 592aba4a6..f1e3376ee 100644 --- a/src/sections/shared/form/RequiredFieldText/RequiredFieldText.tsx +++ b/src/sections/shared/form/RequiredFieldText/RequiredFieldText.tsx @@ -2,9 +2,9 @@ import { RequiredInputSymbol } from '@iqss/dataverse-design-system' import { useTranslation } from 'react-i18next' export function RequiredFieldText() { - const { t } = useTranslation('createDataset') + const { t } = useTranslation('datasetMetadataForm') return ( -

+

{t('requiredFields')}

diff --git a/src/stories/create-dataset/CreateDataset.stories.tsx b/src/stories/create-dataset/CreateDataset.stories.tsx index 0008a5f16..a97097198 100644 --- a/src/stories/create-dataset/CreateDataset.stories.tsx +++ b/src/stories/create-dataset/CreateDataset.stories.tsx @@ -3,9 +3,8 @@ import { CreateDataset } from '../../sections/create-dataset/CreateDataset' import { WithLayout } from '../WithLayout' import { WithI18next } from '../WithI18next' import { DatasetMockRepository } from '../dataset/DatasetMockRepository' -import { MetadataBlockInfoMockRepository } from './MetadataBlockInfoMockRepository' -import { MetadataBlockInfoMockLoadingRepository } from './MetadataBlockInfoMockLoadingRepository' -import { MetadataBlockInfoMockErrorRepository } from './MetadataBlockInfoMockErrorRepository' +import { MetadataBlockInfoMockRepository } from '../shared-mock-repositories/metadata-block-info/MetadataBlockInfoMockRepository' +import { MetadataBlockInfoMockLoadingRepository } from '../shared-mock-repositories/metadata-block-info/MetadataBlockInfoMockLoadingRepository' import { NotImplementedModalProvider } from '../../sections/not-implemented/NotImplementedModalProvider' import { WithLoggedInUser } from '../WithLoggedInUser' @@ -25,27 +24,18 @@ export const Default: Story = { render: () => ( ) } -export const LoadingMetadataBlocksConfiguration: Story = { +export const Loading: Story = { render: () => ( ) } - -export const ErrorLoadingMetadataBlocksConfiguration: Story = { - render: () => ( - - ) -} diff --git a/src/stories/dataset/Dataset.stories.tsx b/src/stories/dataset/Dataset.stories.tsx index 2a49b884a..a3405c4cb 100644 --- a/src/stories/dataset/Dataset.stories.tsx +++ b/src/stories/dataset/Dataset.stories.tsx @@ -49,6 +49,11 @@ export const Created: Story = { /> ) } + +export const MetadataUpdated: Story = { + decorators: [WithLayout, WithDatasetDraftAsOwner, WithLoggedInUser, WithNotImplementedModal], + render: () => +} export const DraftWithAllDatasetPermissions: Story = { decorators: [WithLayout, WithDatasetDraftAsOwner, WithLoggedInUser, WithNotImplementedModal], render: () => diff --git a/src/stories/dataset/DatasetErrorMockRepository.ts b/src/stories/dataset/DatasetErrorMockRepository.ts index ec2f1d74f..bdc0f12ac 100644 --- a/src/stories/dataset/DatasetErrorMockRepository.ts +++ b/src/stories/dataset/DatasetErrorMockRepository.ts @@ -45,4 +45,12 @@ export class DatasetErrorMockRepository implements DatasetMockRepository { }, FakerHelper.loadingTimout()) }) } + + updateMetadata(_datasetId: string | number, _updatedDataset: DatasetDTO): Promise { + return new Promise((_resolve, reject) => { + setTimeout(() => { + reject('Error thrown from mock') + }, FakerHelper.loadingTimout()) + }) + } } diff --git a/src/stories/dataset/DatasetMockRepository.ts b/src/stories/dataset/DatasetMockRepository.ts index f6c8b1f0a..3ef6905f2 100644 --- a/src/stories/dataset/DatasetMockRepository.ts +++ b/src/stories/dataset/DatasetMockRepository.ts @@ -49,4 +49,12 @@ export class DatasetMockRepository implements DatasetRepository { }, FakerHelper.loadingTimout()) }) } + + updateMetadata(_datasetId: string | number, _updatedDataset: DatasetDTO): Promise { + return new Promise((resolve) => { + setTimeout(() => { + resolve() + }, FakerHelper.loadingTimout()) + }) + } } diff --git a/src/stories/edit-dataset-metadata/EditDatasetMetadata.stories.tsx b/src/stories/edit-dataset-metadata/EditDatasetMetadata.stories.tsx new file mode 100644 index 000000000..e2917c1d9 --- /dev/null +++ b/src/stories/edit-dataset-metadata/EditDatasetMetadata.stories.tsx @@ -0,0 +1,39 @@ +import type { StoryObj, Meta } from '@storybook/react' +import { WithLayout } from '../WithLayout' +import { WithI18next } from '../WithI18next' +import { DatasetMockRepository } from '../dataset/DatasetMockRepository' +import { MetadataBlockInfoMockRepository } from '../shared-mock-repositories/metadata-block-info/MetadataBlockInfoMockRepository' +import { MetadataBlockInfoMockLoadingRepository } from '../shared-mock-repositories/metadata-block-info/MetadataBlockInfoMockLoadingRepository' +import { WithLoggedInUser } from '../WithLoggedInUser' +import { EditDatasetMetadata } from '../../sections/edit-dataset-metadata/EditDatasetMetadata' +import { WithDataset } from '../dataset/WithDataset' + +const meta: Meta = { + title: 'Pages/Edit Dataset Metadata', + component: EditDatasetMetadata, + decorators: [WithI18next, WithLayout, WithDataset, WithLoggedInUser], + parameters: { + // Sets the delay for all stories. + chromatic: { delay: 15000, pauseAnimationAtEnd: true } + } +} +export default meta +type Story = StoryObj + +export const Default: Story = { + render: () => ( + + ) +} + +export const Loading: Story = { + render: () => ( + + ) +} diff --git a/src/stories/create-dataset/MetadataBlockInfoMockErrorRepository.ts b/src/stories/shared-mock-repositories/metadata-block-info/MetadataBlockInfoMockErrorRepository.ts similarity index 93% rename from src/stories/create-dataset/MetadataBlockInfoMockErrorRepository.ts rename to src/stories/shared-mock-repositories/metadata-block-info/MetadataBlockInfoMockErrorRepository.ts index 734e0fb3f..69f32d3b5 100644 --- a/src/stories/create-dataset/MetadataBlockInfoMockErrorRepository.ts +++ b/src/stories/shared-mock-repositories/metadata-block-info/MetadataBlockInfoMockErrorRepository.ts @@ -1,7 +1,7 @@ import { MetadataBlockInfoDisplayFormat, MetadataBlockInfo -} from '../../metadata-block-info/domain/models/MetadataBlockInfo' +} from '../../../metadata-block-info/domain/models/MetadataBlockInfo' import { MetadataBlockInfoMockRepository } from './MetadataBlockInfoMockRepository' export class MetadataBlockInfoMockErrorRepository implements MetadataBlockInfoMockRepository { diff --git a/src/stories/create-dataset/MetadataBlockInfoMockLoadingRepository.ts b/src/stories/shared-mock-repositories/metadata-block-info/MetadataBlockInfoMockLoadingRepository.ts similarity index 89% rename from src/stories/create-dataset/MetadataBlockInfoMockLoadingRepository.ts rename to src/stories/shared-mock-repositories/metadata-block-info/MetadataBlockInfoMockLoadingRepository.ts index e69913365..3ff8742eb 100644 --- a/src/stories/create-dataset/MetadataBlockInfoMockLoadingRepository.ts +++ b/src/stories/shared-mock-repositories/metadata-block-info/MetadataBlockInfoMockLoadingRepository.ts @@ -1,7 +1,7 @@ import { MetadataBlockInfoDisplayFormat, MetadataBlockInfo -} from '../../metadata-block-info/domain/models/MetadataBlockInfo' +} from '../../../metadata-block-info/domain/models/MetadataBlockInfo' import { MetadataBlockInfoMockRepository } from './MetadataBlockInfoMockRepository' export class MetadataBlockInfoMockLoadingRepository implements MetadataBlockInfoMockRepository { diff --git a/src/stories/create-dataset/MetadataBlockInfoMockRepository.ts b/src/stories/shared-mock-repositories/metadata-block-info/MetadataBlockInfoMockRepository.ts similarity index 73% rename from src/stories/create-dataset/MetadataBlockInfoMockRepository.ts rename to src/stories/shared-mock-repositories/metadata-block-info/MetadataBlockInfoMockRepository.ts index 6b6c42fd7..0d3e1e7c5 100644 --- a/src/stories/create-dataset/MetadataBlockInfoMockRepository.ts +++ b/src/stories/shared-mock-repositories/metadata-block-info/MetadataBlockInfoMockRepository.ts @@ -1,9 +1,9 @@ -import { MetadataBlockInfoMother } from '../../../tests/component/metadata-block-info/domain/models/MetadataBlockInfoMother' +import { MetadataBlockInfoMother } from '../../../../tests/component/metadata-block-info/domain/models/MetadataBlockInfoMother' import { MetadataBlockInfoDisplayFormat, MetadataBlockInfo -} from '../../metadata-block-info/domain/models/MetadataBlockInfo' -import { MetadataBlockInfoRepository } from '../../metadata-block-info/domain/repositories/MetadataBlockInfoRepository' +} from '../../../metadata-block-info/domain/models/MetadataBlockInfo' +import { MetadataBlockInfoRepository } from '../../../metadata-block-info/domain/repositories/MetadataBlockInfoRepository' export class MetadataBlockInfoMockRepository implements MetadataBlockInfoRepository { getByName(_name: string): Promise { diff --git a/src/stories/shared/dataset-metadata-form/DatasetMetadataForm.stories.tsx b/src/stories/shared/dataset-metadata-form/DatasetMetadataForm.stories.tsx new file mode 100644 index 000000000..ffc54694f --- /dev/null +++ b/src/stories/shared/dataset-metadata-form/DatasetMetadataForm.stories.tsx @@ -0,0 +1,45 @@ +import type { StoryObj, Meta } from '@storybook/react' +import { DatasetMetadataForm } from '../../../sections/shared/form/DatasetMetadataForm' +import { WithI18next } from '../../WithI18next' +import { WithLoggedInUser } from '../../WithLoggedInUser' +import { DatasetMockRepository } from '../../dataset/DatasetMockRepository' +import { MetadataBlockInfoMockRepository } from '../../shared-mock-repositories/metadata-block-info/MetadataBlockInfoMockRepository' +import { DatasetMother } from '../../../../tests/component/dataset/domain/models/DatasetMother' + +const meta: Meta = { + title: 'Sections/Shared/Dataset Metadata Form', + component: DatasetMetadataForm, + decorators: [WithI18next, WithLoggedInUser], + parameters: { + // Sets the delay for all stories. + chromatic: { delay: 15000, pauseAnimationAtEnd: true } + } +} +export default meta +type Story = StoryObj + +export const CreateMode: Story = { + render: () => ( + + ) +} + +const datasetToEditMock = DatasetMother.createRealistic() + +export const EditMode: Story = { + render: () => ( + + ) +} diff --git a/tests/component/dataset/domain/models/DatasetMother.ts b/tests/component/dataset/domain/models/DatasetMother.ts index e1d039a11..16fc4eb1d 100644 --- a/tests/component/dataset/domain/models/DatasetMother.ts +++ b/tests/component/dataset/domain/models/DatasetMother.ts @@ -21,6 +21,10 @@ import { FileSizeUnit } from '../../../../../src/files/domain/models/FileMetadata' import { UpwardHierarchyNodeMother } from '../../../shared/hierarchy/domain/models/UpwardHierarchyNodeMother' +import { + COUNTRY_FIELD_VOCAB_VALUES, + SUBJECT_FIELD_VOCAB_VALUES +} from '../../../metadata-block-info/domain/models/MetadataBlockInfoMother' export class DatasetVersionMother { static create(props?: Partial): DatasetVersion { @@ -498,7 +502,7 @@ export class DatasetMother { publicationDate: '2021-01-01', citationDate: '2023-01-01', title: 'Dataset Title', - subject: ['Subject1', 'Subject2'], + subject: [SUBJECT_FIELD_VOCAB_VALUES[0], SUBJECT_FIELD_VOCAB_VALUES[1]], author: [ { authorName: 'Admin, Dataverse', @@ -532,8 +536,8 @@ export class DatasetMother { fields: { geographicUnit: 'km', geographicCoverage: { - geographicCoverageCountry: 'United States', - geographicCoverageCity: 'Cambridge' + country: COUNTRY_FIELD_VOCAB_VALUES[234], + city: 'Cambridge' } } } @@ -570,7 +574,7 @@ export class DatasetMother { publicationDate: ANONYMIZED_FIELD_VALUE, citationDate: '2023-01-01', title: 'Dataset Title', - subject: ['Subject1', 'Subject2'], + subject: [SUBJECT_FIELD_VOCAB_VALUES[0], SUBJECT_FIELD_VOCAB_VALUES[1]], author: ANONYMIZED_FIELD_VALUE, datasetContact: ANONYMIZED_FIELD_VALUE, dsDescription: [ diff --git a/tests/component/metadata-block-info/domain/models/MetadataBlockInfoMother.ts b/tests/component/metadata-block-info/domain/models/MetadataBlockInfoMother.ts index 34b854445..8489af959 100644 --- a/tests/component/metadata-block-info/domain/models/MetadataBlockInfoMother.ts +++ b/tests/component/metadata-block-info/domain/models/MetadataBlockInfoMother.ts @@ -33,7 +33,7 @@ export class MetadataBlockInfoMother { } } - static getByCollectionIdDisplayedOnCreateTrue(): MetadataBlockInfo[] { + static getByCollectionIdDisplayedOnCreateTrue(block?: MetadataBlockInfo): MetadataBlockInfo[] { return [ { id: 1, @@ -46,118 +46,21 @@ export class MetadataBlockInfoMother { displayName: 'Title', title: 'Title', type: 'TEXT', - typeClass: 'primitive', watermark: '', description: 'The main title of the Dataset', multiple: false, isControlledVocabulary: false, displayFormat: '', isRequired: true, - displayOnCreate: true, - displayOrder: 0 - }, - subtitle: { - name: 'subtitle', - displayName: 'Subtitle', - title: 'Subtitle', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: - 'A secondary title that amplifies or states certain limitations on the main title', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - displayOnCreate: true, - isRequired: false, - displayOrder: 1 - }, - alternativeTitle: { - name: 'alternativeTitle', - displayName: 'Alternative Title', - title: 'Alternative Title', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: - 'Either 1) a title commonly used to refer to the Dataset or 2) an abbreviation of the main title', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: true, - displayOnCreate: true, - displayOrder: 2 - }, - alternativeURL: { - name: 'alternativeURL', - displayName: 'Alternative URL', - title: 'Alternative URL', - type: 'URL', + displayOrder: 0, typeClass: 'primitive', - watermark: 'https://', - description: - 'Another URL where one can view or access the data in the Dataset, e.g. a project or personal webpage', - multiple: false, - isControlledVocabulary: false, - displayFormat: '#VALUE', - isRequired: false, - displayOnCreate: true, - displayOrder: 3 - }, - otherId: { - name: 'otherId', - displayName: 'Other Identifier', - title: 'Other Identifier', - type: 'NONE', - typeClass: 'compound', - watermark: '', - description: - "Another unique identifier for the Dataset (e.g. producer's or another repository's identifier)", - multiple: true, - isControlledVocabulary: false, - displayFormat: ':', - isRequired: false, - displayOrder: 4, - displayOnCreate: true, - childMetadataFields: { - otherIdAgency: { - name: 'otherIdAgency', - displayName: 'Other Identifier Agency', - title: 'Agency', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: 'The name of the agency that generated the other identifier', - multiple: false, - isControlledVocabulary: false, - displayFormat: '#VALUE', - isRequired: false, - displayOnCreate: true, - displayOrder: 5 - }, - otherIdValue: { - name: 'otherIdValue', - displayName: 'Other Identifier Identifier', - title: 'Identifier', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: 'Another identifier uniquely identifies the Dataset', - multiple: false, - isControlledVocabulary: false, - displayFormat: '#VALUE', - isRequired: false, - displayOnCreate: true, - displayOrder: 6 - } - } + displayOnCreate: true }, author: { name: 'author', displayName: 'Author', title: 'Author', type: 'NONE', - typeClass: 'compound', watermark: '', description: 'The entity, e.g. a person or organization, that created the Dataset', multiple: true, @@ -165,6 +68,7 @@ export class MetadataBlockInfoMother { displayFormat: '', isRequired: true, displayOrder: 7, + typeClass: 'compound', displayOnCreate: true, childMetadataFields: { authorName: { @@ -172,7 +76,6 @@ export class MetadataBlockInfoMother { displayName: 'Author Name', title: 'Name', type: 'TEXT', - typeClass: 'primitive', watermark: '1) Family Name, Given Name or 2) Organization XYZ', description: "The name of the author, such as the person's name or the name of an organization", @@ -180,15 +83,15 @@ export class MetadataBlockInfoMother { isControlledVocabulary: false, displayFormat: '#VALUE', isRequired: true, - displayOnCreate: true, - displayOrder: 8 + displayOrder: 8, + typeClass: 'primitive', + displayOnCreate: true }, authorAffiliation: { name: 'authorAffiliation', displayName: 'Author Affiliation', title: 'Affiliation', type: 'TEXT', - typeClass: 'primitive', watermark: 'Organization XYZ', description: "The name of the entity affiliated with the author, e.g. an organization's name", @@ -196,24 +99,20 @@ export class MetadataBlockInfoMother { isControlledVocabulary: false, displayFormat: '(#VALUE)', isRequired: false, - displayOnCreate: true, - displayOrder: 9 + displayOrder: 9, + typeClass: 'primitive', + displayOnCreate: true }, authorIdentifierScheme: { name: 'authorIdentifierScheme', displayName: 'Author Identifier Type', title: 'Identifier Type', type: 'TEXT', - typeClass: 'controlledVocabulary', watermark: '', description: 'The type of identifier that uniquely identifies the author (e.g. ORCID, ISNI)', multiple: false, isControlledVocabulary: true, - displayFormat: '- #VALUE:', - isRequired: false, - displayOnCreate: true, - displayOrder: 10, controlledVocabularyValues: [ 'ORCID', 'ISNI', @@ -223,22 +122,27 @@ export class MetadataBlockInfoMother { 'DAI', 'ResearcherID', 'ScopusID' - ] + ], + displayFormat: '- #VALUE:', + isRequired: false, + displayOrder: 10, + typeClass: 'controlledVocabulary', + displayOnCreate: true }, authorIdentifier: { name: 'authorIdentifier', displayName: 'Author Identifier', title: 'Identifier', type: 'TEXT', - typeClass: 'primitive', watermark: '', description: 'Uniquely identifies the author when paired with an identifier type', multiple: false, isControlledVocabulary: false, displayFormat: '#VALUE', isRequired: false, - displayOnCreate: true, - displayOrder: 11 + displayOrder: 11, + typeClass: 'primitive', + displayOnCreate: true } } }, @@ -247,7 +151,6 @@ export class MetadataBlockInfoMother { displayName: 'Point of Contact', title: 'Point of Contact', type: 'NONE', - typeClass: 'compound', watermark: '', description: 'The entity, e.g. a person or organization, that users of the Dataset can contact with questions', @@ -256,6 +159,7 @@ export class MetadataBlockInfoMother { displayFormat: '', isRequired: true, displayOrder: 12, + typeClass: 'compound', displayOnCreate: true, childMetadataFields: { datasetContactName: { @@ -263,7 +167,6 @@ export class MetadataBlockInfoMother { displayName: 'Point of Contact Name', title: 'Name', type: 'TEXT', - typeClass: 'primitive', watermark: '1) FamilyName, GivenName or 2) Organization', description: "The name of the point of contact, e.g. the person's name or the name of an organization", @@ -271,15 +174,15 @@ export class MetadataBlockInfoMother { isControlledVocabulary: false, displayFormat: '#VALUE', isRequired: false, - displayOnCreate: true, - displayOrder: 13 + displayOrder: 13, + typeClass: 'primitive', + displayOnCreate: true }, datasetContactAffiliation: { name: 'datasetContactAffiliation', displayName: 'Point of Contact Affiliation', title: 'Affiliation', type: 'TEXT', - typeClass: 'primitive', watermark: 'Organization XYZ', description: "The name of the entity affiliated with the point of contact, e.g. an organization's name", @@ -287,23 +190,24 @@ export class MetadataBlockInfoMother { isControlledVocabulary: false, displayFormat: '(#VALUE)', isRequired: false, - displayOnCreate: true, - displayOrder: 14 + displayOrder: 14, + typeClass: 'primitive', + displayOnCreate: true }, datasetContactEmail: { name: 'datasetContactEmail', displayName: 'Point of Contact E-mail', title: 'E-mail', type: 'EMAIL', - typeClass: 'primitive', watermark: 'name@email.xyz', description: "The point of contact's email address", multiple: false, isControlledVocabulary: false, displayFormat: '#EMAIL', isRequired: true, - displayOnCreate: true, - displayOrder: 15 + displayOrder: 15, + typeClass: 'primitive', + displayOnCreate: true } } }, @@ -312,7 +216,6 @@ export class MetadataBlockInfoMother { displayName: 'Description', title: 'Description', type: 'NONE', - typeClass: 'compound', watermark: '', description: 'A summary describing the purpose, nature, and scope of the Dataset', multiple: true, @@ -320,6 +223,7 @@ export class MetadataBlockInfoMother { displayFormat: '', isRequired: true, displayOrder: 16, + typeClass: 'compound', displayOnCreate: true, childMetadataFields: { dsDescriptionValue: { @@ -327,22 +231,21 @@ export class MetadataBlockInfoMother { displayName: 'Description Text', title: 'Text', type: 'TEXTBOX', - typeClass: 'primitive', watermark: '', description: 'A summary describing the purpose, nature, and scope of the Dataset', multiple: false, isControlledVocabulary: false, displayFormat: '#VALUE', isRequired: true, - displayOnCreate: true, - displayOrder: 17 + displayOrder: 17, + typeClass: 'primitive', + displayOnCreate: true }, dsDescriptionDate: { name: 'dsDescriptionDate', displayName: 'Description Date', title: 'Date', type: 'DATE', - typeClass: 'primitive', watermark: 'YYYY-MM-DD', description: 'The date when the description was added to the Dataset. If the Dataset contains more than one description, e.g. the data producer supplied one description and the data repository supplied another, this date is used to distinguish between the descriptions', @@ -350,8 +253,9 @@ export class MetadataBlockInfoMother { isControlledVocabulary: false, displayFormat: '(#VALUE)', isRequired: false, - displayOnCreate: true, - displayOrder: 18 + displayOrder: 18, + typeClass: 'primitive', + displayOnCreate: true } } }, @@ -360,38 +264,22 @@ export class MetadataBlockInfoMother { displayName: 'Subject', title: 'Subject', type: 'TEXT', - typeClass: 'controlledVocabulary', watermark: '', description: 'The area of study relevant to the Dataset', multiple: true, isControlledVocabulary: true, + controlledVocabularyValues: SUBJECT_FIELD_VOCAB_VALUES, displayFormat: '', isRequired: true, displayOrder: 19, - displayOnCreate: true, - controlledVocabularyValues: [ - 'Agricultural Sciences', - 'Arts and Humanities', - 'Astronomy and Astrophysics', - 'Business and Management', - 'Chemistry', - 'Computer and Information Science', - 'Earth and Environmental Sciences', - 'Engineering', - 'Law', - 'Mathematical Sciences', - 'Medicine, Health and Life Sciences', - 'Physics', - 'Social Sciences', - 'Other' - ] + typeClass: 'controlledVocabulary', + displayOnCreate: true }, keyword: { name: 'keyword', displayName: 'Keyword', title: 'Keyword', type: 'NONE', - typeClass: 'compound', watermark: '', description: 'A key term that describes an important aspect of the Dataset and information about any controlled vocabulary used', @@ -400,6 +288,7 @@ export class MetadataBlockInfoMother { displayFormat: '', isRequired: false, displayOrder: 20, + typeClass: 'compound', displayOnCreate: true, childMetadataFields: { keywordValue: { @@ -407,22 +296,36 @@ export class MetadataBlockInfoMother { displayName: 'Keyword Term', title: 'Term', type: 'TEXT', - typeClass: 'primitive', watermark: '', description: 'A key term that describes important aspects of the Dataset', multiple: false, isControlledVocabulary: false, displayFormat: '#VALUE', isRequired: false, - displayOnCreate: true, - displayOrder: 21 + displayOrder: 21, + typeClass: 'primitive', + displayOnCreate: true + }, + keywordTermURI: { + name: 'keywordTermURI', + displayName: 'Keyword Term URI', + title: 'Term URI', + type: 'URL', + watermark: 'https://', + description: 'A URI that points to the web presence of the Keyword Term', + multiple: false, + isControlledVocabulary: false, + displayFormat: '#VALUE', + isRequired: false, + displayOrder: 22, + typeClass: 'primitive', + displayOnCreate: true }, keywordVocabulary: { name: 'keywordVocabulary', displayName: 'Keyword Controlled Vocabulary Name', title: 'Controlled Vocabulary Name', type: 'TEXT', - typeClass: 'primitive', watermark: '', description: 'The controlled vocabulary used for the keyword term (e.g. LCSH, MeSH)', @@ -430,15 +333,15 @@ export class MetadataBlockInfoMother { isControlledVocabulary: false, displayFormat: '(#VALUE)', isRequired: false, - displayOnCreate: true, - displayOrder: 22 + displayOrder: 23, + typeClass: 'primitive', + displayOnCreate: true }, keywordVocabularyURI: { name: 'keywordVocabularyURI', displayName: 'Keyword Controlled Vocabulary URL', title: 'Controlled Vocabulary URL', type: 'URL', - typeClass: 'primitive', watermark: 'https://', description: "The URL where one can access information about the term's controlled vocabulary", @@ -446,1077 +349,355 @@ export class MetadataBlockInfoMother { isControlledVocabulary: false, displayFormat: '#VALUE', isRequired: false, - displayOnCreate: true, - displayOrder: 23 + displayOrder: 24, + typeClass: 'primitive', + displayOnCreate: true } } }, - language: { - name: 'language', - displayName: 'Language', - title: 'Language', - type: 'TEXT', - typeClass: 'controlledVocabulary', + publication: { + name: 'publication', + displayName: 'Related Publication', + title: 'Related Publication', + type: 'NONE', watermark: '', - description: "A language that the Dataset's files is written in", + description: + 'The article or report that uses the data in the Dataset. The full list of related publications will be displayed on the metadata tab', multiple: true, - isControlledVocabulary: true, + isControlledVocabulary: false, displayFormat: '', isRequired: false, - displayOrder: 34, - displayOnCreate: true, - controlledVocabularyValues: [ - 'Abkhaz', - 'Afar', - 'Afrikaans', - 'Akan', - 'Albanian', - 'Amharic', - 'Arabic', - 'Aragonese', - 'Armenian', - 'Assamese', - 'Avaric', - 'Avestan', - 'Aymara', - 'Azerbaijani', - 'Bambara', - 'Bashkir', - 'Basque', - 'Belarusian', - 'Bengali, Bangla', - 'Bihari', - 'Bislama', - 'Bosnian', - 'Breton', - 'Bulgarian', - 'Burmese', - 'Catalan,Valencian', - 'Chamorro', - 'Chechen', - 'Chichewa, Chewa, Nyanja', - 'Chinese', - 'Chuvash', - 'Cornish', - 'Corsican', - 'Cree', - 'Croatian', - 'Czech', - 'Danish', - 'Divehi, Dhivehi, Maldivian', - 'Dutch', - 'Dzongkha', - 'English', - 'Esperanto', - 'Estonian', - 'Ewe', - 'Faroese', - 'Fijian', - 'Finnish', - 'French', - 'Fula, Fulah, Pulaar, Pular', - 'Galician', - 'Georgian', - 'German', - 'Greek (modern)', - 'Guaraní', - 'Gujarati', - 'Haitian, Haitian Creole', - 'Hausa', - 'Hebrew (modern)', - 'Herero', - 'Hindi', - 'Hiri Motu', - 'Hungarian', - 'Interlingua', - 'Indonesian', - 'Interlingue', - 'Irish', - 'Igbo', - 'Inupiaq', - 'Ido', - 'Icelandic', - 'Italian', - 'Inuktitut', - 'Japanese', - 'Javanese', - 'Kalaallisut, Greenlandic', - 'Kannada', - 'Kanuri', - 'Kashmiri', - 'Kazakh', - 'Khmer', - 'Kikuyu, Gikuyu', - 'Kinyarwanda', - 'Kyrgyz', - 'Komi', - 'Kongo', - 'Korean', - 'Kurdish', - 'Kwanyama, Kuanyama', - 'Latin', - 'Luxembourgish, Letzeburgesch', - 'Ganda', - 'Limburgish, Limburgan, Limburger', - 'Lingala', - 'Lao', - 'Lithuanian', - 'Luba-Katanga', - 'Latvian', - 'Manx', - 'Macedonian', - 'Malagasy', - 'Malay', - 'Malayalam', - 'Maltese', - 'Māori', - 'Marathi (Marāṭhī)', - 'Marshallese', - 'Mixtepec Mixtec', - 'Mongolian', - 'Nauru', - 'Navajo, Navaho', - 'Northern Ndebele', - 'Nepali', - 'Ndonga', - 'Norwegian Bokmål', - 'Norwegian Nynorsk', - 'Norwegian', - 'Nuosu', - 'Southern Ndebele', - 'Occitan', - 'Ojibwe, Ojibwa', - 'Old Church Slavonic,Church Slavonic,Old Bulgarian', - 'Oromo', - 'Oriya', - 'Ossetian, Ossetic', - 'Panjabi, Punjabi', - 'Pāli', - 'Persian (Farsi)', - 'Polish', - 'Pashto, Pushto', - 'Portuguese', - 'Quechua', - 'Romansh', - 'Kirundi', - 'Romanian', - 'Russian', - 'Sanskrit (Saṁskṛta)', - 'Sardinian', - 'Sindhi', - 'Northern Sami', - 'Samoan', - 'Sango', - 'Serbian', - 'Scottish Gaelic, Gaelic', - 'Shona', - 'Sinhala, Sinhalese', - 'Slovak', - 'Slovene', - 'Somali', - 'Southern Sotho', - 'Spanish, Castilian', - 'Sundanese', - 'Swahili', - 'Swati', - 'Swedish', - 'Tamil', - 'Telugu', - 'Tajik', - 'Thai', - 'Tigrinya', - 'Tibetan Standard, Tibetan, Central', - 'Turkmen', - 'Tagalog', - 'Tswana', - 'Tonga (Tonga Islands)', - 'Turkish', - 'Tsonga', - 'Tatar', - 'Twi', - 'Tahitian', - 'Uyghur, Uighur', - 'Ukrainian', - 'Urdu', - 'Uzbek', - 'Venda', - 'Vietnamese', - 'Volapük', - 'Walloon', - 'Welsh', - 'Wolof', - 'Western Frisian', - 'Xhosa', - 'Yiddish', - 'Yoruba', - 'Zhuang, Chuang', - 'Zulu', - 'Not applicable' - ] - }, - producer: { - name: 'producer', - displayName: 'Producer', - title: 'Producer', - type: 'NONE', - typeClass: 'compound', - watermark: '', - description: - 'The entity, such a person or organization, managing the finances or other administrative processes involved in the creation of the Dataset', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOrder: 35, + displayOrder: 29, + typeClass: 'compound', displayOnCreate: true, childMetadataFields: { - producerName: { - name: 'producerName', - displayName: 'Producer Name', - title: 'Name', - type: 'TEXT', - typeClass: 'primitive', - watermark: '1) FamilyName, GivenName or 2) Organization', - description: - "The name of the entity, e.g. the person's name or the name of an organization", + publicationCitation: { + name: 'publicationCitation', + displayName: 'Related Publication Citation', + title: 'Citation', + type: 'TEXTBOX', + watermark: '', + description: 'The full bibliographic citation for the related publication', multiple: false, isControlledVocabulary: false, displayFormat: '#VALUE', - isRequired: true, - displayOnCreate: true, - displayOrder: 36 + isRequired: false, + displayOrder: 30, + typeClass: 'primitive', + displayOnCreate: true }, - producerAffiliation: { - name: 'producerAffiliation', - displayName: 'Producer Affiliation', - title: 'Affiliation', + publicationIDType: { + name: 'publicationIDType', + displayName: 'Related Publication Identifier Type', + title: 'Identifier Type', type: 'TEXT', - typeClass: 'primitive', - watermark: 'Organization XYZ', + watermark: '', description: - "The name of the entity affiliated with the producer, e.g. an organization's name", + 'The type of identifier that uniquely identifies a related publication', multiple: false, - isControlledVocabulary: false, - displayFormat: '(#VALUE)', + isControlledVocabulary: true, + controlledVocabularyValues: [ + 'ark', + 'arXiv', + 'bibcode', + 'cstr', + 'doi', + 'ean13', + 'eissn', + 'handle', + 'isbn', + 'issn', + 'istc', + 'lissn', + 'lsid', + 'pmid', + 'purl', + 'upc', + 'url', + 'urn', + 'DASH-NRS' + ], + displayFormat: '#VALUE: ', isRequired: false, - displayOnCreate: true, - displayOrder: 37 + displayOrder: 31, + typeClass: 'controlledVocabulary', + displayOnCreate: true }, - producerAbbreviation: { - name: 'producerAbbreviation', - displayName: 'Producer Abbreviated Name', - title: 'Abbreviated Name', + publicationIDNumber: { + name: 'publicationIDNumber', + displayName: 'Related Publication Identifier', + title: 'Identifier', type: 'TEXT', - typeClass: 'primitive', watermark: '', - description: "The producer's abbreviated name (e.g. IQSS, ICPSR)", + description: 'The identifier for a related publication', multiple: false, isControlledVocabulary: false, - displayFormat: '(#VALUE)', + displayFormat: '#VALUE', isRequired: false, - displayOnCreate: true, - displayOrder: 38 + displayOrder: 32, + typeClass: 'primitive', + displayOnCreate: true }, - producerURL: { - name: 'producerURL', - displayName: 'Producer URL', + publicationURL: { + name: 'publicationURL', + displayName: 'Related Publication URL', title: 'URL', type: 'URL', - typeClass: 'primitive', watermark: 'https://', - description: "The URL of the producer's website", + description: + 'The URL form of the identifier entered in the Identifier field, e.g. the DOI URL if a DOI was entered in the Identifier field. Used to display what was entered in the ID Type and ID Number fields as a link. If what was entered in the Identifier field has no URL form, the URL of the publication webpage is used, e.g. a journal article webpage', multiple: false, isControlledVocabulary: false, displayFormat: '#VALUE', isRequired: false, - displayOnCreate: true, - displayOrder: 39 - }, - producerLogoURL: { - name: 'producerLogoURL', - displayName: 'Producer Logo URL', - title: 'Logo URL', - type: 'URL', + displayOrder: 33, typeClass: 'primitive', - watermark: 'https://', - description: "The URL of the producer's logo", - multiple: false, - isControlledVocabulary: false, - displayFormat: '
', - isRequired: false, - displayOnCreate: true, - displayOrder: 40 + displayOnCreate: true } } }, - distributor: { - name: 'distributor', - displayName: 'Distributor', - title: 'Distributor', - type: 'NONE', - typeClass: 'compound', + notesText: { + name: 'notesText', + displayName: 'Notes', + title: 'Notes', + type: 'TEXTBOX', + watermark: '', + description: 'Additional information about the Dataset', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: false, + displayOrder: 34, + typeClass: 'primitive', + displayOnCreate: true + } + } + }, + { + id: 2, + name: 'geospatial', + displayName: 'Geospatial Metadata', + displayOnCreate: false, + metadataFields: { + geographicUnit: { + name: 'geographicUnit', + displayName: 'Geographic Unit', + title: 'Geographic Unit', + type: 'TEXT', watermark: '', description: - 'The entity, such as a person or organization, designated to generate copies of the Dataset, including any editions or revisions', - multiple: true, + 'Lowest level of geographic aggregation covered by the Dataset, e.g., village, county, region.', + multiple: false, isControlledVocabulary: false, displayFormat: '', isRequired: false, - displayOrder: 49, - displayOnCreate: true, - childMetadataFields: { - distributorName: { - name: 'distributorName', - displayName: 'Distributor Name', - title: 'Name', - type: 'TEXT', - typeClass: 'primitive', - watermark: '1) FamilyName, GivenName or 2) Organization', - description: - "The name of the entity, e.g. the person's name or the name of an organization", - multiple: false, - isControlledVocabulary: false, - displayFormat: '#VALUE', - isRequired: false, - displayOnCreate: true, - displayOrder: 50 - }, - distributorAffiliation: { - name: 'distributorAffiliation', - displayName: 'Distributor Affiliation', - title: 'Affiliation', - type: 'TEXT', - typeClass: 'primitive', - watermark: 'Organization XYZ', - description: - "The name of the entity affiliated with the distributor, e.g. an organization's name", - multiple: false, - isControlledVocabulary: false, - displayFormat: '(#VALUE)', - isRequired: false, - displayOnCreate: true, - displayOrder: 51 - }, - distributorAbbreviation: { - name: 'distributorAbbreviation', - displayName: 'Distributor Abbreviated Name', - title: 'Abbreviated Name', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: "The distributor's abbreviated name (e.g. IQSS, ICPSR)", - multiple: false, - isControlledVocabulary: false, - displayFormat: '(#VALUE)', - isRequired: false, - displayOnCreate: true, - displayOrder: 52 - }, - distributorURL: { - name: 'distributorURL', - displayName: 'Distributor URL', - title: 'URL', - type: 'URL', - typeClass: 'primitive', - watermark: 'https://', - description: "The URL of the distributor's webpage", - multiple: false, - isControlledVocabulary: false, - displayFormat: '#VALUE', - isRequired: false, - displayOnCreate: true, - displayOrder: 53 - }, - distributorLogoURL: { - name: 'distributorLogoURL', - displayName: 'Distributor Logo URL', - title: 'Logo URL', - type: 'URL', - typeClass: 'primitive', - watermark: 'https://', - description: - "The URL of the distributor's logo image, used to show the image on the Dataset's page", - multiple: false, - isControlledVocabulary: false, - displayFormat: '
', - isRequired: false, - displayOnCreate: true, - displayOrder: 54 - } - } - }, - notesText: { - name: 'notesText', - displayName: 'Notes', - title: 'Notes', - type: 'TEXTBOX', + displayOrder: 5, typeClass: 'primitive', - watermark: '', - description: 'Additional information about the Dataset', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOrder: 33, displayOnCreate: true }, - someComposeFieldNotMultiple: { - name: 'someComposeFieldNotMultiple', - displayName: 'Composed field not multiple', - title: 'Composed field not multiple', + geographicCoverage: { + name: 'geographicCoverage', + displayName: 'Geographic Coverage', + title: 'Geographic Coverage', type: 'NONE', - typeClass: 'compound', watermark: '', - description: 'Some composed field not multiple', + description: + 'Information on the geographic coverage of the data. Includes the total geographic scope of the data.', multiple: false, isControlledVocabulary: false, displayFormat: '', isRequired: true, - displayOrder: 7, + displayOrder: 0, + typeClass: 'compound', displayOnCreate: true, childMetadataFields: { - foo: { - name: 'foo', - displayName: 'Foo', - title: 'Foo', + country: { + name: 'country', + displayName: 'Geographic Coverage Country / Nation', + title: 'Country / Nation', type: 'TEXT', - typeClass: 'primitive', - watermark: '1) Family Name, Given Name or 2) Organization XYZ', - description: 'Some foo', + watermark: '', + description: 'The country or nation that the Dataset is about.', multiple: false, - isControlledVocabulary: false, - displayFormat: '#VALUE', + isControlledVocabulary: true, + controlledVocabularyValues: COUNTRY_FIELD_VOCAB_VALUES, + displayFormat: '#VALUE, ', isRequired: true, - displayOnCreate: true, - displayOrder: 99 + displayOrder: 1, + typeClass: 'controlledVocabulary', + displayOnCreate: false + }, + state: { + name: 'state', + displayName: 'Geographic Coverage State / Province', + title: 'State / Province', + type: 'TEXT', + watermark: '', + description: + 'The state or province that the Dataset is about. Use GeoNames for correct spelling and avoid abbreviations.', + multiple: false, + isControlledVocabulary: false, + displayFormat: '#VALUE, ', + isRequired: false, + displayOrder: 2, + typeClass: 'primitive', + displayOnCreate: false }, - bar: { - name: 'bar', - displayName: 'Bar', - title: 'Bar', + city: { + name: 'city', + displayName: 'Geographic Coverage City', + title: 'City', type: 'TEXT', + watermark: '', + description: + 'The name of the city that the Dataset is about. Use GeoNames for correct spelling and avoid abbreviations.', + multiple: false, + isControlledVocabulary: false, + displayFormat: '#VALUE, ', + isRequired: false, + displayOrder: 3, typeClass: 'primitive', - watermark: '1) Family Name, Given Name or 2) Organization XYZ', - description: 'Some bar', + displayOnCreate: false + }, + otherGeographicCoverage: { + name: 'otherGeographicCoverage', + displayName: 'Geographic Coverage Other', + title: 'Other', + type: 'TEXT', + watermark: '', + description: 'Other information on the geographic coverage of the data.', multiple: false, isControlledVocabulary: false, - displayFormat: '#VALUE', + displayFormat: '#VALUE, ', isRequired: false, - displayOnCreate: true, - displayOrder: 98 + displayOrder: 4, + typeClass: 'primitive', + displayOnCreate: false } } - }, - somePrimitiveMultipleField: { - name: 'primitiveMultipleField', - displayName: 'Primitive Multiple Field', - title: 'Primitive Multiple Field', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: - 'Either 1) a title commonly used to refer to the Dataset or 2) an abbreviation of the main title', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 2 - }, - somePrimitiveMultipleFieldTextarea: { - name: 'primitiveMultipleField Textarea', - displayName: 'Primitive Multiple Field Textarea', - title: 'Primitive Multiple Field Textarea', - type: 'TEXTBOX', - typeClass: 'primitive', - watermark: '', - description: - 'Either 1) a title commonly used to refer to the Dataset or 2) an abbreviation of the main title', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 2 - }, - someControlledVocabularyFieldNotMultiple: { - name: 'someControlledVocabularyFieldNotMultiple', - displayName: 'Controlled Vocabulary Field Not Multiple', - title: 'Controlled Vocabulary Field Not Multiple', - type: 'TEXT', - typeClass: 'controlledVocabulary', - watermark: '', - description: 'Some controlled vocabulary field not multiple', - multiple: false, - isControlledVocabulary: true, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 3, - controlledVocabularyValues: ['value1', 'value2', 'value3'] } } }, + ...(block ? [block] : []) + ] + } + + static getByCollectionIdDisplayedOnCreateFalse(): MetadataBlockInfo[] { + return [ { - id: 4, - name: 'astrophysics', - displayName: 'Astronomy and Astrophysics Metadata', + id: 1, + name: 'citation', + displayName: 'Citation Metadata', displayOnCreate: true, metadataFields: { - astroType: { - name: 'astroType', - displayName: 'Type', - title: 'Type', + title: { + name: 'title', + displayName: 'Title', + title: 'Title', type: 'TEXT', - typeClass: 'controlledVocabulary', watermark: '', - description: 'The nature or genre of the content of the files in the dataset.', - multiple: true, - isControlledVocabulary: true, + description: 'The main title of the Dataset', + multiple: false, + isControlledVocabulary: false, displayFormat: '', - isRequired: false, + isRequired: true, displayOrder: 0, - displayOnCreate: true, - controlledVocabularyValues: [ - 'Image', - 'Mosaic', - 'EventList', - 'Spectrum', - 'Cube', - 'Table', - 'Catalog', - 'LightCurve', - 'Simulation', - 'Figure', - 'Artwork', - 'Animation', - 'PrettyPicture', - 'Documentation', - 'Other', - 'Library', - 'Press Release', - 'Facsimile', - 'Historical', - 'Observation', - 'Object', - 'Value', - 'ValuePair', - 'Survey' - ] - }, - astroFacility: { - name: 'astroFacility', - displayName: 'Facility', - title: 'Facility', - type: 'TEXT', typeClass: 'primitive', - watermark: '', - description: 'The observatory or facility where the data was obtained. ', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 1 + displayOnCreate: true }, - astroInstrument: { - name: 'astroInstrument', - displayName: 'Instrument', - title: 'Instrument', + subtitle: { + name: 'subtitle', + displayName: 'Subtitle', + title: 'Subtitle', type: 'TEXT', - typeClass: 'primitive', watermark: '', - description: 'The instrument used to collect the data.', - multiple: true, + description: + 'A secondary title that amplifies or states certain limitations on the main title', + multiple: false, isControlledVocabulary: false, displayFormat: '', isRequired: false, - displayOnCreate: true, - displayOrder: 2 + displayOrder: 1, + typeClass: 'primitive', + displayOnCreate: false }, - astroObject: { - name: 'astroObject', - displayName: 'Object', - title: 'Object', + alternativeTitle: { + name: 'alternativeTitle', + displayName: 'Alternative Title', + title: 'Alternative Title', type: 'TEXT', - typeClass: 'primitive', watermark: '', description: - 'Astronomical Objects represented in the data (Given as SIMBAD recognizable names preferred).', + 'Either 1) a title commonly used to refer to the Dataset or 2) an abbreviation of the main title', multiple: true, isControlledVocabulary: false, displayFormat: '', isRequired: false, - displayOnCreate: true, - displayOrder: 3 - }, - 'resolution.Spatial': { - name: 'resolution.Spatial', - displayName: 'Spatial Resolution', - title: 'Spatial Resolution', - type: 'TEXT', + displayOrder: 2, typeClass: 'primitive', - watermark: '', - description: - 'The spatial (angular) resolution that is typical of the observations, in decimal degrees.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 4 + displayOnCreate: false }, - 'resolution.Spectral': { - name: 'resolution.Spectral', - displayName: 'Spectral Resolution', - title: 'Spectral Resolution', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', + alternativeURL: { + name: 'alternativeURL', + displayName: 'Alternative URL', + title: 'Alternative URL', + type: 'URL', + watermark: 'https://', description: - 'The spectral resolution that is typical of the observations, given as the ratio \\u03bb/\\u0394\\u03bb.', + 'Another URL where one can view or access the data in the Dataset, e.g. a project or personal webpage', multiple: false, isControlledVocabulary: false, - displayFormat: '', + displayFormat: '#VALUE', isRequired: false, - displayOnCreate: true, - displayOrder: 5 - }, - 'resolution.Temporal': { - name: 'resolution.Temporal', - displayName: 'Time Resolution', - title: 'Time Resolution', - type: 'TEXT', + displayOrder: 3, typeClass: 'primitive', - watermark: '', - description: - 'The temporal resolution that is typical of the observations, given in seconds.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 6 + displayOnCreate: false }, - 'coverage.Spectral.Bandpass': { - name: 'coverage.Spectral.Bandpass', - displayName: 'Bandpass', - title: 'Bandpass', - type: 'TEXT', - typeClass: 'primitive', + otherId: { + name: 'otherId', + displayName: 'Other Identifier', + title: 'Other Identifier', + type: 'NONE', watermark: '', - description: 'Conventional bandpass name', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 7 - }, - 'coverage.Spectral.CentralWavelength': { - name: 'coverage.Spectral.CentralWavelength', - displayName: 'Central Wavelength (m)', - title: 'Central Wavelength (m)', - type: 'FLOAT', - typeClass: 'primitive', - watermark: 'Enter a floating-point number.', - description: 'The central wavelength of the spectral bandpass, in meters.', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 8 - }, - 'coverage.Spectral.Wavelength': { - name: 'coverage.Spectral.Wavelength', - displayName: 'Wavelength Range', - title: 'Wavelength Range', - type: 'NONE', - typeClass: 'compound', - watermark: 'Enter a floating-point number.', - description: 'The minimum and maximum wavelength of the spectral bandpass.', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 9, - childMetadataFields: { - 'coverage.Spectral.MinimumWavelength': { - name: 'coverage.Spectral.MinimumWavelength', - displayName: 'Wavelength Range Minimum (m)', - title: 'Minimum (m)', - type: 'FLOAT', - typeClass: 'primitive', - watermark: 'Enter a floating-point number.', - description: 'The minimum wavelength of the spectral bandpass, in meters.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 10 - }, - 'coverage.Spectral.MaximumWavelength': { - name: 'coverage.Spectral.MaximumWavelength', - displayName: 'Wavelength Range Maximum (m)', - title: 'Maximum (m)', - type: 'FLOAT', - typeClass: 'primitive', - watermark: 'Enter a floating-point number.', - description: 'The maximum wavelength of the spectral bandpass, in meters.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 11 - } - } - }, - 'coverage.Temporal': { - name: 'coverage.Temporal', - displayName: 'Dataset Date Range', - title: 'Dataset Date Range', - type: 'NONE', - typeClass: 'compound', - watermark: '', - description: ' Time period covered by the data.', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 12, - childMetadataFields: { - 'coverage.Temporal.StartTime': { - name: 'coverage.Temporal.StartTime', - displayName: 'Dataset Date Range Start', - title: 'Start', - type: 'DATE', - typeClass: 'primitive', - watermark: 'YYYY-MM-DD', - description: 'Dataset Start Date', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 13 - }, - 'coverage.Temporal.StopTime': { - name: 'coverage.Temporal.StopTime', - displayName: 'Dataset Date Range End', - title: 'End', - type: 'DATE', - typeClass: 'primitive', - watermark: 'YYYY-MM-DD', - description: 'Dataset End Date', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 14 - } - } - }, - 'coverage.Spatial': { - name: 'coverage.Spatial', - displayName: 'Sky Coverage', - title: 'Sky Coverage', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: 'The sky coverage of the data object.', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 15 - }, - 'coverage.Depth': { - name: 'coverage.Depth', - displayName: 'Depth Coverage', - title: 'Depth Coverage', - type: 'FLOAT', - typeClass: 'primitive', - watermark: 'Enter a floating-point number.', - description: 'The (typical) depth coverage, or sensitivity, of the data object in Jy.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 16 - }, - 'coverage.ObjectDensity': { - name: 'coverage.ObjectDensity', - displayName: 'Object Density', - title: 'Object Density', - type: 'FLOAT', - typeClass: 'primitive', - watermark: 'Enter a floating-point number.', - description: - 'The (typical) density of objects, catalog entries, telescope pointings, etc., on the sky, in number per square degree.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 17 - }, - 'coverage.ObjectCount': { - name: 'coverage.ObjectCount', - displayName: 'Object Count', - title: 'Object Count', - type: 'INT', - typeClass: 'primitive', - watermark: 'Enter an integer.', - description: 'The total number of objects, catalog entries, etc., in the data object.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 18 - }, - 'coverage.SkyFraction': { - name: 'coverage.SkyFraction', - displayName: 'Fraction of Sky', - title: 'Fraction of Sky', - type: 'FLOAT', - typeClass: 'primitive', - watermark: 'Enter a floating-point number.', - description: - 'The fraction of the sky represented in the observations, ranging from 0 to 1.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 19 - }, - 'coverage.Polarization': { - name: 'coverage.Polarization', - displayName: 'Polarization', - title: 'Polarization', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: 'The polarization coverage', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 20 - }, - redshiftType: { - name: 'redshiftType', - displayName: 'RedshiftType', - title: 'RedshiftType', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: - 'RedshiftType string C "Redshift"; or "Optical" or "Radio" definitions of Doppler velocity used in the data object.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 21 - }, - 'resolution.Redshift': { - name: 'resolution.Redshift', - displayName: 'Redshift Resolution', - title: 'Redshift Resolution', - type: 'FLOAT', - typeClass: 'primitive', - watermark: 'Enter a floating-point number.', - description: - 'The resolution in redshift (unitless) or Doppler velocity (km/s) in the data object.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 22 - }, - 'coverage.RedshiftValue': { - name: 'coverage.RedshiftValue', - displayName: 'Redshift Value', - title: 'Redshift Value', - type: 'FLOAT', - typeClass: 'compound', - watermark: 'Enter a floating-point number.', - description: - 'The value of the redshift (unitless) or Doppler velocity (km/s in the data object.', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 23, - childMetadataFields: { - 'coverage.Redshift.MinimumValue': { - name: 'coverage.Redshift.MinimumValue', - displayName: 'Redshift Value Minimum', - title: 'Minimum', - type: 'FLOAT', - typeClass: 'primitive', - watermark: 'Enter a floating-point number.', - description: - 'The minimum value of the redshift (unitless) or Doppler velocity (km/s in the data object.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 24 - }, - 'coverage.Redshift.MaximumValue': { - name: 'coverage.Redshift.MaximumValue', - displayName: 'Redshift Value Maximum', - title: 'Maximum', - type: 'FLOAT', - typeClass: 'primitive', - watermark: 'Enter a floating-point number.', - description: - 'The maximum value of the redshift (unitless) or Doppler velocity (km/s in the data object.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 25 - } - } - } - } - } - ] - } - - static getByCollectionIdDisplayedOnCreateFalse(): MetadataBlockInfo[] { - return [ - { - id: 1, - name: 'citation', - displayName: 'Citation Metadata', - displayOnCreate: true, - metadataFields: { - title: { - name: 'title', - displayName: 'Title', - title: 'Title', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: 'The main title of the Dataset', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: true, - displayOnCreate: true, - displayOrder: 0 - }, - subtitle: { - name: 'subtitle', - displayName: 'Subtitle', - title: 'Subtitle', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: - 'A secondary title that amplifies or states certain limitations on the main title', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - displayOnCreate: true, - isRequired: false, - displayOrder: 1 - }, - alternativeTitle: { - name: 'alternativeTitle', - displayName: 'Alternative Title', - title: 'Alternative Title', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: - 'Either 1) a title commonly used to refer to the Dataset or 2) an abbreviation of the main title', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 2 - }, - alternativeURL: { - name: 'alternativeURL', - displayName: 'Alternative URL', - title: 'Alternative URL', - type: 'URL', - typeClass: 'primitive', - watermark: 'https://', - description: - 'Another URL where one can view or access the data in the Dataset, e.g. a project or personal webpage', - multiple: false, - isControlledVocabulary: false, - displayFormat: '#VALUE', - isRequired: false, - displayOnCreate: true, - displayOrder: 3 - }, - otherId: { - name: 'otherId', - displayName: 'Other Identifier', - title: 'Other Identifier', - type: 'NONE', - typeClass: 'compound', - watermark: '', - description: - "Another unique identifier for the Dataset (e.g. producer's or another repository's identifier)", + description: + "Another unique identifier for the Dataset (e.g. producer's or another repository's identifier)", multiple: true, isControlledVocabulary: false, displayFormat: ':', isRequired: false, displayOrder: 4, - displayOnCreate: true, + typeClass: 'compound', + displayOnCreate: false, childMetadataFields: { otherIdAgency: { name: 'otherIdAgency', displayName: 'Other Identifier Agency', title: 'Agency', type: 'TEXT', - typeClass: 'primitive', watermark: '', description: 'The name of the agency that generated the other identifier', multiple: false, isControlledVocabulary: false, displayFormat: '#VALUE', isRequired: false, - displayOnCreate: true, - displayOrder: 5 + displayOrder: 5, + typeClass: 'primitive', + displayOnCreate: false }, otherIdValue: { name: 'otherIdValue', displayName: 'Other Identifier Identifier', title: 'Identifier', type: 'TEXT', - typeClass: 'primitive', watermark: '', description: 'Another identifier uniquely identifies the Dataset', multiple: false, isControlledVocabulary: false, displayFormat: '#VALUE', isRequired: false, - displayOnCreate: true, - displayOrder: 6 + displayOrder: 6, + typeClass: 'primitive', + displayOnCreate: false } } }, @@ -1525,7 +706,6 @@ export class MetadataBlockInfoMother { displayName: 'Author', title: 'Author', type: 'NONE', - typeClass: 'compound', watermark: '', description: 'The entity, e.g. a person or organization, that created the Dataset', multiple: true, @@ -1533,6 +713,7 @@ export class MetadataBlockInfoMother { displayFormat: '', isRequired: true, displayOrder: 7, + typeClass: 'compound', displayOnCreate: true, childMetadataFields: { authorName: { @@ -1540,7 +721,6 @@ export class MetadataBlockInfoMother { displayName: 'Author Name', title: 'Name', type: 'TEXT', - typeClass: 'primitive', watermark: '1) Family Name, Given Name or 2) Organization XYZ', description: "The name of the author, such as the person's name or the name of an organization", @@ -1548,15 +728,15 @@ export class MetadataBlockInfoMother { isControlledVocabulary: false, displayFormat: '#VALUE', isRequired: true, - displayOnCreate: true, - displayOrder: 8 + displayOrder: 8, + typeClass: 'primitive', + displayOnCreate: true }, authorAffiliation: { name: 'authorAffiliation', displayName: 'Author Affiliation', title: 'Affiliation', type: 'TEXT', - typeClass: 'primitive', watermark: 'Organization XYZ', description: "The name of the entity affiliated with the author, e.g. an organization's name", @@ -1564,24 +744,20 @@ export class MetadataBlockInfoMother { isControlledVocabulary: false, displayFormat: '(#VALUE)', isRequired: false, - displayOnCreate: true, - displayOrder: 9 + displayOrder: 9, + typeClass: 'primitive', + displayOnCreate: true }, authorIdentifierScheme: { name: 'authorIdentifierScheme', displayName: 'Author Identifier Type', title: 'Identifier Type', type: 'TEXT', - typeClass: 'controlledVocabulary', watermark: '', description: 'The type of identifier that uniquely identifies the author (e.g. ORCID, ISNI)', multiple: false, isControlledVocabulary: true, - displayFormat: '- #VALUE:', - isRequired: false, - displayOnCreate: true, - displayOrder: 10, controlledVocabularyValues: [ 'ORCID', 'ISNI', @@ -1591,22 +767,27 @@ export class MetadataBlockInfoMother { 'DAI', 'ResearcherID', 'ScopusID' - ] + ], + displayFormat: '- #VALUE:', + isRequired: false, + displayOrder: 10, + typeClass: 'controlledVocabulary', + displayOnCreate: true }, authorIdentifier: { name: 'authorIdentifier', displayName: 'Author Identifier', title: 'Identifier', type: 'TEXT', - typeClass: 'primitive', watermark: '', description: 'Uniquely identifies the author when paired with an identifier type', multiple: false, isControlledVocabulary: false, displayFormat: '#VALUE', isRequired: false, - displayOnCreate: true, - displayOrder: 11 + displayOrder: 11, + typeClass: 'primitive', + displayOnCreate: true } } }, @@ -1615,7 +796,6 @@ export class MetadataBlockInfoMother { displayName: 'Point of Contact', title: 'Point of Contact', type: 'NONE', - typeClass: 'compound', watermark: '', description: 'The entity, e.g. a person or organization, that users of the Dataset can contact with questions', @@ -1624,6 +804,7 @@ export class MetadataBlockInfoMother { displayFormat: '', isRequired: true, displayOrder: 12, + typeClass: 'compound', displayOnCreate: true, childMetadataFields: { datasetContactName: { @@ -1631,7 +812,6 @@ export class MetadataBlockInfoMother { displayName: 'Point of Contact Name', title: 'Name', type: 'TEXT', - typeClass: 'primitive', watermark: '1) FamilyName, GivenName or 2) Organization', description: "The name of the point of contact, e.g. the person's name or the name of an organization", @@ -1639,15 +819,15 @@ export class MetadataBlockInfoMother { isControlledVocabulary: false, displayFormat: '#VALUE', isRequired: false, - displayOnCreate: true, - displayOrder: 13 + displayOrder: 13, + typeClass: 'primitive', + displayOnCreate: true }, datasetContactAffiliation: { name: 'datasetContactAffiliation', displayName: 'Point of Contact Affiliation', title: 'Affiliation', type: 'TEXT', - typeClass: 'primitive', watermark: 'Organization XYZ', description: "The name of the entity affiliated with the point of contact, e.g. an organization's name", @@ -1655,23 +835,24 @@ export class MetadataBlockInfoMother { isControlledVocabulary: false, displayFormat: '(#VALUE)', isRequired: false, - displayOnCreate: true, - displayOrder: 14 + displayOrder: 14, + typeClass: 'primitive', + displayOnCreate: true }, datasetContactEmail: { name: 'datasetContactEmail', displayName: 'Point of Contact E-mail', title: 'E-mail', type: 'EMAIL', - typeClass: 'primitive', watermark: 'name@email.xyz', description: "The point of contact's email address", multiple: false, isControlledVocabulary: false, displayFormat: '#EMAIL', isRequired: true, - displayOnCreate: true, - displayOrder: 15 + displayOrder: 15, + typeClass: 'primitive', + displayOnCreate: true } } }, @@ -1680,7 +861,6 @@ export class MetadataBlockInfoMother { displayName: 'Description', title: 'Description', type: 'NONE', - typeClass: 'compound', watermark: '', description: 'A summary describing the purpose, nature, and scope of the Dataset', multiple: true, @@ -1688,6 +868,7 @@ export class MetadataBlockInfoMother { displayFormat: '', isRequired: true, displayOrder: 16, + typeClass: 'compound', displayOnCreate: true, childMetadataFields: { dsDescriptionValue: { @@ -1695,22 +876,21 @@ export class MetadataBlockInfoMother { displayName: 'Description Text', title: 'Text', type: 'TEXTBOX', - typeClass: 'primitive', watermark: '', description: 'A summary describing the purpose, nature, and scope of the Dataset', multiple: false, isControlledVocabulary: false, displayFormat: '#VALUE', isRequired: true, - displayOnCreate: true, - displayOrder: 17 + displayOrder: 17, + typeClass: 'primitive', + displayOnCreate: true }, dsDescriptionDate: { name: 'dsDescriptionDate', displayName: 'Description Date', title: 'Date', type: 'DATE', - typeClass: 'primitive', watermark: 'YYYY-MM-DD', description: 'The date when the description was added to the Dataset. If the Dataset contains more than one description, e.g. the data producer supplied one description and the data repository supplied another, this date is used to distinguish between the descriptions', @@ -1718,8 +898,9 @@ export class MetadataBlockInfoMother { isControlledVocabulary: false, displayFormat: '(#VALUE)', isRequired: false, - displayOnCreate: true, - displayOrder: 18 + displayOrder: 18, + typeClass: 'primitive', + displayOnCreate: true } } }, @@ -1728,38 +909,22 @@ export class MetadataBlockInfoMother { displayName: 'Subject', title: 'Subject', type: 'TEXT', - typeClass: 'controlledVocabulary', watermark: '', description: 'The area of study relevant to the Dataset', multiple: true, isControlledVocabulary: true, + controlledVocabularyValues: SUBJECT_FIELD_VOCAB_VALUES, displayFormat: '', isRequired: true, displayOrder: 19, - displayOnCreate: true, - controlledVocabularyValues: [ - 'Agricultural Sciences', - 'Arts and Humanities', - 'Astronomy and Astrophysics', - 'Business and Management', - 'Chemistry', - 'Computer and Information Science', - 'Earth and Environmental Sciences', - 'Engineering', - 'Law', - 'Mathematical Sciences', - 'Medicine, Health and Life Sciences', - 'Physics', - 'Social Sciences', - 'Other' - ] + typeClass: 'controlledVocabulary', + displayOnCreate: true }, keyword: { name: 'keyword', displayName: 'Keyword', title: 'Keyword', type: 'NONE', - typeClass: 'compound', watermark: '', description: 'A key term that describes an important aspect of the Dataset and information about any controlled vocabulary used', @@ -1768,6 +933,7 @@ export class MetadataBlockInfoMother { displayFormat: '', isRequired: false, displayOrder: 20, + typeClass: 'compound', displayOnCreate: true, childMetadataFields: { keywordValue: { @@ -1775,22 +941,36 @@ export class MetadataBlockInfoMother { displayName: 'Keyword Term', title: 'Term', type: 'TEXT', - typeClass: 'primitive', watermark: '', description: 'A key term that describes important aspects of the Dataset', multiple: false, isControlledVocabulary: false, displayFormat: '#VALUE', isRequired: false, - displayOnCreate: true, - displayOrder: 21 + displayOrder: 21, + typeClass: 'primitive', + displayOnCreate: true + }, + keywordTermURI: { + name: 'keywordTermURI', + displayName: 'Keyword Term URI', + title: 'Term URI', + type: 'URL', + watermark: 'https://', + description: 'A URI that points to the web presence of the Keyword Term', + multiple: false, + isControlledVocabulary: false, + displayFormat: '#VALUE', + isRequired: false, + displayOrder: 22, + typeClass: 'primitive', + displayOnCreate: true }, keywordVocabulary: { name: 'keywordVocabulary', displayName: 'Keyword Controlled Vocabulary Name', title: 'Controlled Vocabulary Name', type: 'TEXT', - typeClass: 'primitive', watermark: '', description: 'The controlled vocabulary used for the keyword term (e.g. LCSH, MeSH)', @@ -1798,15 +978,15 @@ export class MetadataBlockInfoMother { isControlledVocabulary: false, displayFormat: '(#VALUE)', isRequired: false, - displayOnCreate: true, - displayOrder: 22 + displayOrder: 23, + typeClass: 'primitive', + displayOnCreate: true }, keywordVocabularyURI: { name: 'keywordVocabularyURI', displayName: 'Keyword Controlled Vocabulary URL', title: 'Controlled Vocabulary URL', type: 'URL', - typeClass: 'primitive', watermark: 'https://', description: "The URL where one can access information about the term's controlled vocabulary", @@ -1814,8 +994,9 @@ export class MetadataBlockInfoMother { isControlledVocabulary: false, displayFormat: '#VALUE', isRequired: false, - displayOnCreate: true, - displayOrder: 23 + displayOrder: 24, + typeClass: 'primitive', + displayOnCreate: true } } }, @@ -1824,7 +1005,6 @@ export class MetadataBlockInfoMother { displayName: 'Topic Classification', title: 'Topic Classification', type: 'NONE', - typeClass: 'compound', watermark: '', description: 'Indicates a broad, important topic or subject that the Dataset covers and information about any controlled vocabulary used', @@ -1832,30 +1012,30 @@ export class MetadataBlockInfoMother { isControlledVocabulary: false, displayFormat: '', isRequired: false, - displayOrder: 24, - displayOnCreate: true, + displayOrder: 25, + typeClass: 'compound', + displayOnCreate: false, childMetadataFields: { topicClassValue: { name: 'topicClassValue', displayName: 'Topic Classification Term', title: 'Term', type: 'TEXT', - typeClass: 'primitive', watermark: '', description: 'A topic or subject term', multiple: false, isControlledVocabulary: false, displayFormat: '#VALUE', isRequired: false, - displayOnCreate: true, - displayOrder: 25 + displayOrder: 26, + typeClass: 'primitive', + displayOnCreate: false }, topicClassVocab: { name: 'topicClassVocab', displayName: 'Topic Classification Controlled Vocabulary Name', title: 'Controlled Vocabulary Name', type: 'TEXT', - typeClass: 'primitive', watermark: '', description: 'The controlled vocabulary used for the keyword term (e.g. LCSH, MeSH)', @@ -1863,15 +1043,15 @@ export class MetadataBlockInfoMother { isControlledVocabulary: false, displayFormat: '(#VALUE)', isRequired: false, - displayOnCreate: true, - displayOrder: 26 + displayOrder: 27, + typeClass: 'primitive', + displayOnCreate: false }, topicClassVocabURI: { name: 'topicClassVocabURI', displayName: 'Topic Classification Controlled Vocabulary URL', title: 'Controlled Vocabulary URL', type: 'URL', - typeClass: 'primitive', watermark: 'https://', description: "The URL where one can access information about the term's controlled vocabulary", @@ -1879,8 +1059,9 @@ export class MetadataBlockInfoMother { isControlledVocabulary: false, displayFormat: '#VALUE', isRequired: false, - displayOnCreate: true, - displayOrder: 27 + displayOrder: 28, + typeClass: 'primitive', + displayOnCreate: false } } }, @@ -1889,7 +1070,6 @@ export class MetadataBlockInfoMother { displayName: 'Related Publication', title: 'Related Publication', type: 'NONE', - typeClass: 'compound', watermark: '', description: 'The article or report that uses the data in the Dataset. The full list of related publications will be displayed on the metadata tab', @@ -1897,7 +1077,8 @@ export class MetadataBlockInfoMother { isControlledVocabulary: false, displayFormat: '', isRequired: false, - displayOrder: 28, + displayOrder: 29, + typeClass: 'compound', displayOnCreate: true, childMetadataFields: { publicationCitation: { @@ -1905,31 +1086,26 @@ export class MetadataBlockInfoMother { displayName: 'Related Publication Citation', title: 'Citation', type: 'TEXTBOX', - typeClass: 'primitive', watermark: '', description: 'The full bibliographic citation for the related publication', multiple: false, isControlledVocabulary: false, displayFormat: '#VALUE', isRequired: false, - displayOnCreate: true, - displayOrder: 29 + displayOrder: 30, + typeClass: 'primitive', + displayOnCreate: true }, publicationIDType: { name: 'publicationIDType', displayName: 'Related Publication Identifier Type', title: 'Identifier Type', type: 'TEXT', - typeClass: 'controlledVocabulary', watermark: '', description: 'The type of identifier that uniquely identifies a related publication', multiple: false, isControlledVocabulary: true, - displayFormat: '#VALUE: ', - isRequired: false, - displayOrder: 30, - displayOnCreate: true, controlledVocabularyValues: [ 'ark', 'arXiv', @@ -1950,29 +1126,33 @@ export class MetadataBlockInfoMother { 'url', 'urn', 'DASH-NRS' - ] + ], + displayFormat: '#VALUE: ', + isRequired: false, + displayOrder: 31, + typeClass: 'controlledVocabulary', + displayOnCreate: true }, publicationIDNumber: { name: 'publicationIDNumber', displayName: 'Related Publication Identifier', title: 'Identifier', type: 'TEXT', - typeClass: 'primitive', watermark: '', description: 'The identifier for a related publication', multiple: false, isControlledVocabulary: false, displayFormat: '#VALUE', isRequired: false, - displayOnCreate: true, - displayOrder: 31 + displayOrder: 32, + typeClass: 'primitive', + displayOnCreate: true }, publicationURL: { name: 'publicationURL', displayName: 'Related Publication URL', title: 'URL', type: 'URL', - typeClass: 'primitive', watermark: 'https://', description: 'The URL form of the identifier entered in the Identifier field, e.g. the DOI URL if a DOI was entered in the Identifier field. Used to display what was entered in the ID Type and ID Number fields as a link. If what was entered in the Identifier field has no URL form, the URL of the publication webpage is used, e.g. a journal article webpage', @@ -1980,8 +1160,9 @@ export class MetadataBlockInfoMother { isControlledVocabulary: false, displayFormat: '#VALUE', isRequired: false, - displayOnCreate: true, - displayOrder: 32 + displayOrder: 33, + typeClass: 'primitive', + displayOnCreate: true } } }, @@ -1990,14 +1171,14 @@ export class MetadataBlockInfoMother { displayName: 'Notes', title: 'Notes', type: 'TEXTBOX', - typeClass: 'primitive', watermark: '', description: 'Additional information about the Dataset', multiple: false, isControlledVocabulary: false, displayFormat: '', isRequired: false, - displayOrder: 33, + displayOrder: 34, + typeClass: 'primitive', displayOnCreate: true }, language: { @@ -2005,15 +1186,10 @@ export class MetadataBlockInfoMother { displayName: 'Language', title: 'Language', type: 'TEXT', - typeClass: 'controlledVocabulary', watermark: '', description: "A language that the Dataset's files is written in", multiple: true, isControlledVocabulary: true, - displayFormat: '', - isRequired: false, - displayOrder: 34, - displayOnCreate: true, controlledVocabularyValues: [ 'Abkhaz', 'Afar', @@ -2201,2636 +1377,900 @@ export class MetadataBlockInfoMother { 'Zhuang, Chuang', 'Zulu', 'Not applicable' - ] - }, - producer: { - name: 'producer', - displayName: 'Producer', - title: 'Producer', - type: 'NONE', - typeClass: 'compound', - watermark: '', - description: - 'The entity, such a person or organization, managing the finances or other administrative processes involved in the creation of the Dataset', - multiple: true, - isControlledVocabulary: false, + ], displayFormat: '', isRequired: false, displayOrder: 35, - displayOnCreate: true, - childMetadataFields: { - producerName: { - name: 'producerName', - displayName: 'Producer Name', - title: 'Name', - type: 'TEXT', - typeClass: 'primitive', - watermark: '1) FamilyName, GivenName or 2) Organization', - description: - "The name of the entity, e.g. the person's name or the name of an organization", - multiple: false, - isControlledVocabulary: false, - displayFormat: '#VALUE', - isRequired: true, - displayOnCreate: true, - displayOrder: 36 - }, - producerAffiliation: { - name: 'producerAffiliation', - displayName: 'Producer Affiliation', - title: 'Affiliation', - type: 'TEXT', - typeClass: 'primitive', - watermark: 'Organization XYZ', - description: - "The name of the entity affiliated with the producer, e.g. an organization's name", - multiple: false, - isControlledVocabulary: false, - displayFormat: '(#VALUE)', - isRequired: false, - displayOnCreate: true, - displayOrder: 37 - }, - producerAbbreviation: { - name: 'producerAbbreviation', - displayName: 'Producer Abbreviated Name', - title: 'Abbreviated Name', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: "The producer's abbreviated name (e.g. IQSS, ICPSR)", - multiple: false, - isControlledVocabulary: false, - displayFormat: '(#VALUE)', - isRequired: false, - displayOnCreate: true, - displayOrder: 38 - }, - producerURL: { - name: 'producerURL', - displayName: 'Producer URL', - title: 'URL', - type: 'URL', - typeClass: 'primitive', - watermark: 'https://', - description: "The URL of the producer's website", - multiple: false, - isControlledVocabulary: false, - displayFormat: '#VALUE', - isRequired: false, - displayOnCreate: true, - displayOrder: 39 - }, - producerLogoURL: { - name: 'producerLogoURL', - displayName: 'Producer Logo URL', - title: 'Logo URL', - type: 'URL', - typeClass: 'primitive', - watermark: 'https://', - description: "The URL of the producer's logo", - multiple: false, - isControlledVocabulary: false, - displayFormat: '
', - isRequired: false, - displayOnCreate: true, - displayOrder: 40 - } - } - }, - productionDate: { - name: 'productionDate', - displayName: 'Production Date', - title: 'Production Date', - type: 'DATE', - typeClass: 'primitive', - watermark: 'YYYY-MM-DD', - description: - 'The date when the data were produced (not distributed, published, or archived)', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 41 - }, - productionPlace: { - name: 'productionPlace', - displayName: 'Production Location', - title: 'Production Location', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: - 'The location where the data and any related materials were produced or collected', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 42 - }, - contributor: { - name: 'contributor', - displayName: 'Contributor', - title: 'Contributor', - type: 'NONE', - typeClass: 'compound', - watermark: '', - description: - 'The entity, such as a person or organization, responsible for collecting, managing, or otherwise contributing to the development of the Dataset', - multiple: true, - isControlledVocabulary: false, - displayFormat: ':', - isRequired: false, - displayOrder: 43, - displayOnCreate: true, - childMetadataFields: { - contributorType: { - name: 'contributorType', - displayName: 'Contributor Type', - title: 'Type', - type: 'TEXT', - typeClass: 'controlledVocabulary', - watermark: '', - description: 'Indicates the type of contribution made to the dataset', - multiple: false, - isControlledVocabulary: true, - displayFormat: '#VALUE ', - isRequired: false, - displayOrder: 44, - displayOnCreate: true, - controlledVocabularyValues: [ - 'Data Collector', - 'Data Curator', - 'Data Manager', - 'Editor', - 'Funder', - 'Hosting Institution', - 'Project Leader', - 'Project Manager', - 'Project Member', - 'Related Person', - 'Researcher', - 'Research Group', - 'Rights Holder', - 'Sponsor', - 'Supervisor', - 'Work Package Leader', - 'Other' - ] - }, - contributorName: { - name: 'contributorName', - displayName: 'Contributor Name', - title: 'Name', - type: 'TEXT', - typeClass: 'primitive', - watermark: '1) FamilyName, GivenName or 2) Organization', - description: - "The name of the contributor, e.g. the person's name or the name of an organization", - multiple: false, - isControlledVocabulary: false, - displayFormat: '#VALUE', - isRequired: false, - displayOnCreate: true, - displayOrder: 45 - } - } + typeClass: 'controlledVocabulary', + displayOnCreate: false }, - grantNumber: { - name: 'grantNumber', - displayName: 'Funding Information', - title: 'Funding Information', - type: 'NONE', - typeClass: 'compound', - watermark: '', - description: "Information about the Dataset's financial support", - multiple: true, - isControlledVocabulary: false, - displayFormat: ':', - isRequired: false, - displayOrder: 46, - displayOnCreate: true, - childMetadataFields: { - grantNumberAgency: { - name: 'grantNumberAgency', - displayName: 'Funding Information Agency', - title: 'Agency', - type: 'TEXT', - typeClass: 'primitive', - watermark: 'Organization XYZ', - description: 'The agency that provided financial support for the Dataset', - multiple: false, - isControlledVocabulary: false, - displayFormat: '#VALUE', - isRequired: false, - displayOnCreate: true, - displayOrder: 47 - }, - grantNumberValue: { - name: 'grantNumberValue', - displayName: 'Funding Information Identifier', - title: 'Identifier', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: - 'The grant identifier or contract identifier of the agency that provided financial support for the Dataset', - multiple: false, - isControlledVocabulary: false, - displayFormat: '#VALUE', - isRequired: false, - displayOnCreate: true, - displayOrder: 48 - } - } - }, - distributor: { - name: 'distributor', - displayName: 'Distributor', - title: 'Distributor', - type: 'NONE', - typeClass: 'compound', - watermark: '', - description: - 'The entity, such as a person or organization, designated to generate copies of the Dataset, including any editions or revisions', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOrder: 49, - displayOnCreate: true, - childMetadataFields: { - distributorName: { - name: 'distributorName', - displayName: 'Distributor Name', - title: 'Name', - type: 'TEXT', - typeClass: 'primitive', - watermark: '1) FamilyName, GivenName or 2) Organization', - description: - "The name of the entity, e.g. the person's name or the name of an organization", - multiple: false, - isControlledVocabulary: false, - displayFormat: '#VALUE', - isRequired: false, - displayOnCreate: true, - displayOrder: 50 - }, - distributorAffiliation: { - name: 'distributorAffiliation', - displayName: 'Distributor Affiliation', - title: 'Affiliation', - type: 'TEXT', - typeClass: 'primitive', - watermark: 'Organization XYZ', - description: - "The name of the entity affiliated with the distributor, e.g. an organization's name", - multiple: false, - isControlledVocabulary: false, - displayFormat: '(#VALUE)', - isRequired: false, - displayOnCreate: true, - displayOrder: 51 - }, - distributorAbbreviation: { - name: 'distributorAbbreviation', - displayName: 'Distributor Abbreviated Name', - title: 'Abbreviated Name', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: "The distributor's abbreviated name (e.g. IQSS, ICPSR)", - multiple: false, - isControlledVocabulary: false, - displayFormat: '(#VALUE)', - isRequired: false, - displayOnCreate: true, - displayOrder: 52 - }, - distributorURL: { - name: 'distributorURL', - displayName: 'Distributor URL', - title: 'URL', - type: 'URL', - typeClass: 'primitive', - watermark: 'https://', - description: "The URL of the distributor's webpage", - multiple: false, - isControlledVocabulary: false, - displayFormat: '#VALUE', - isRequired: false, - displayOnCreate: true, - displayOrder: 53 - }, - distributorLogoURL: { - name: 'distributorLogoURL', - displayName: 'Distributor Logo URL', - title: 'Logo URL', - type: 'URL', - typeClass: 'primitive', - watermark: 'https://', - description: - "The URL of the distributor's logo image, used to show the image on the Dataset's page", - multiple: false, - isControlledVocabulary: false, - displayFormat: '
', - isRequired: false, - displayOnCreate: true, - displayOrder: 54 - } - } - }, - distributionDate: { - name: 'distributionDate', - displayName: 'Distribution Date', - title: 'Distribution Date', - type: 'DATE', - typeClass: 'primitive', - watermark: 'YYYY-MM-DD', - description: - 'The date when the Dataset was made available for distribution/presentation', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOrder: 55, - displayOnCreate: false - }, - depositor: { - name: 'depositor', - displayName: 'Depositor', - title: 'Depositor', - type: 'TEXT', - typeClass: 'primitive', - watermark: '1) FamilyName, GivenName or 2) Organization', - description: - 'The entity, such as a person or organization, that deposited the Dataset in the repository', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOrder: 56, - displayOnCreate: false - }, - dateOfDeposit: { - name: 'dateOfDeposit', - displayName: 'Deposit Date', - title: 'Deposit Date', - type: 'DATE', - typeClass: 'primitive', - watermark: 'YYYY-MM-DD', - description: 'The date when the Dataset was deposited into the repository', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOrder: 57, - displayOnCreate: false - }, - timePeriodCovered: { - name: 'timePeriodCovered', - displayName: 'Time Period', - title: 'Time Period', - type: 'NONE', - typeClass: 'compound', - watermark: '', - description: - 'The time period that the data refer to. Also known as span. This is the time period covered by the data, not the dates of coding, collecting data, or making documents machine-readable', - multiple: true, - isControlledVocabulary: false, - displayFormat: ';', - isRequired: false, - displayOrder: 58, - displayOnCreate: true, - childMetadataFields: { - timePeriodCoveredStart: { - name: 'timePeriodCoveredStart', - displayName: 'Time Period Start Date', - title: 'Start Date', - type: 'DATE', - typeClass: 'primitive', - watermark: 'YYYY-MM-DD', - description: 'The start date of the time period that the data refer to', - multiple: false, - isControlledVocabulary: false, - displayFormat: '#NAME: #VALUE ', - isRequired: false, - displayOnCreate: true, - displayOrder: 59 - }, - timePeriodCoveredEnd: { - name: 'timePeriodCoveredEnd', - displayName: 'Time Period End Date', - title: 'End Date', - type: 'DATE', - typeClass: 'primitive', - watermark: 'YYYY-MM-DD', - description: 'The end date of the time period that the data refer to', - multiple: false, - isControlledVocabulary: false, - displayFormat: '#NAME: #VALUE ', - isRequired: false, - displayOnCreate: true, - displayOrder: 60 - } - } - }, - dateOfCollection: { - name: 'dateOfCollection', - displayName: 'Date of Collection', - title: 'Date of Collection', - type: 'NONE', - typeClass: 'compound', - watermark: '', - description: 'The dates when the data were collected or generated', - multiple: true, - isControlledVocabulary: false, - displayFormat: ';', - isRequired: false, - displayOrder: 61, - displayOnCreate: true, - childMetadataFields: { - dateOfCollectionStart: { - name: 'dateOfCollectionStart', - displayName: 'Date of Collection Start Date', - title: 'Start Date', - type: 'DATE', - typeClass: 'primitive', - watermark: 'YYYY-MM-DD', - description: 'The date when the data collection started', - multiple: false, - isControlledVocabulary: false, - displayFormat: '#NAME: #VALUE ', - isRequired: false, - displayOnCreate: true, - displayOrder: 62 - }, - dateOfCollectionEnd: { - name: 'dateOfCollectionEnd', - displayName: 'Date of Collection End Date', - title: 'End Date', - type: 'DATE', - typeClass: 'primitive', - watermark: 'YYYY-MM-DD', - description: 'The date when the data collection ended', - multiple: false, - isControlledVocabulary: false, - displayFormat: '#NAME: #VALUE ', - isRequired: false, - displayOnCreate: true, - displayOrder: 63 - } - } - }, - kindOfData: { - name: 'kindOfData', - displayName: 'Data Type', - title: 'Data Type', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: - 'The type of data included in the files (e.g. survey data, clinical data, or machine-readable text)', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOrder: 64, - displayOnCreate: false - }, - series: { - name: 'series', - displayName: 'Series', - title: 'Series', - type: 'NONE', - typeClass: 'compound', - watermark: '', - description: 'Information about the dataset series to which the Dataset belong', - multiple: true, - isControlledVocabulary: false, - displayFormat: ':', - isRequired: false, - displayOrder: 65, - displayOnCreate: true, - childMetadataFields: { - seriesName: { - name: 'seriesName', - displayName: 'Series Name', - title: 'Name', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: 'The name of the dataset series', - multiple: false, - isControlledVocabulary: false, - displayFormat: '#VALUE', - isRequired: false, - displayOnCreate: true, - displayOrder: 66 - }, - seriesInformation: { - name: 'seriesInformation', - displayName: 'Series Information', - title: 'Information', - type: 'TEXTBOX', - typeClass: 'primitive', - watermark: '', - description: - 'Can include 1) a history of the series and 2) a summary of features that apply to the series', - multiple: false, - isControlledVocabulary: false, - displayFormat: '#VALUE', - isRequired: false, - displayOnCreate: true, - displayOrder: 67 - } - } - }, - software: { - name: 'software', - displayName: 'Software', - title: 'Software', - type: 'NONE', - typeClass: 'compound', - watermark: '', - description: 'Information about the software used to generate the Dataset', - multiple: true, - isControlledVocabulary: false, - displayFormat: ',', - isRequired: false, - displayOrder: 68, - displayOnCreate: true, - childMetadataFields: { - softwareName: { - name: 'softwareName', - displayName: 'Software Name', - title: 'Name', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: 'The name of software used to generate the Dataset', - multiple: false, - isControlledVocabulary: false, - displayFormat: '#VALUE', - isRequired: false, - displayOnCreate: true, - displayOrder: 69 - }, - softwareVersion: { - name: 'softwareVersion', - displayName: 'Software Version', - title: 'Version', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: 'The version of the software used to generate the Dataset, e.g. 4.11', - multiple: false, - isControlledVocabulary: false, - displayFormat: '#NAME: #VALUE', - isRequired: false, - displayOnCreate: true, - displayOrder: 70 - } - } - }, - relatedMaterial: { - name: 'relatedMaterial', - displayName: 'Related Material', - title: 'Related Material', - type: 'TEXTBOX', - typeClass: 'primitive', - watermark: '', - description: - 'Information, such as a persistent ID or citation, about the material related to the Dataset, such as appendices or sampling information available outside of the Dataset', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOrder: 71, - displayOnCreate: false - }, - relatedDatasets: { - name: 'relatedDatasets', - displayName: 'Related Dataset', - title: 'Related Dataset', - type: 'TEXTBOX', - typeClass: 'primitive', - watermark: '', - description: - "Information, such as a persistent ID or citation, about a related dataset, such as previous research on the Dataset's subject", - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOrder: 72, - displayOnCreate: false - }, - otherReferences: { - name: 'otherReferences', - displayName: 'Other Reference', - title: 'Other Reference', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: - 'Information, such as a persistent ID or citation, about another type of resource that provides background or supporting material to the Dataset', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOrder: 73, - displayOnCreate: false - }, - dataSources: { - name: 'dataSources', - displayName: 'Data Source', - title: 'Data Source', - type: 'TEXTBOX', - typeClass: 'primitive', - watermark: '', - description: - 'Information, such as a persistent ID or citation, about sources of the Dataset (e.g. a book, article, serial, or machine-readable data file)', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOrder: 74, - displayOnCreate: false - }, - originOfSources: { - name: 'originOfSources', - displayName: 'Origin of Historical Sources', - title: 'Origin of Historical Sources', - type: 'TEXTBOX', - typeClass: 'primitive', - watermark: '', - description: - 'For historical sources, the origin and any rules followed in establishing them as sources', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOrder: 75, - displayOnCreate: false - }, - characteristicOfSources: { - name: 'characteristicOfSources', - displayName: 'Characteristic of Sources', - title: 'Characteristic of Sources', - type: 'TEXTBOX', - typeClass: 'primitive', - watermark: '', - description: 'Characteristics not already noted elsewhere', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOrder: 76, - displayOnCreate: false - }, - accessToSources: { - name: 'accessToSources', - displayName: 'Documentation and Access to Sources', - title: 'Documentation and Access to Sources', - type: 'TEXTBOX', - typeClass: 'primitive', - watermark: '', - description: - '1) Methods or procedures for accessing data sources and 2) any special permissions needed for access', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOrder: 77, - displayOnCreate: false - } - } - }, - { - id: 2, - name: 'geospatial', - displayName: 'Geospatial Metadata', - displayOnCreate: false, - metadataFields: { - geographicCoverage: { - name: 'geographicCoverage', - displayName: 'Geographic Coverage', - title: 'Geographic Coverage', - type: 'NONE', - typeClass: 'compound', - watermark: '', - description: - 'Information on the geographic coverage of the data. Includes the total geographic scope of the data.', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOrder: 0, - displayOnCreate: true, - childMetadataFields: { - country: { - name: 'country', - displayName: 'Geographic Coverage Country / Nation', - title: 'Country / Nation', - type: 'TEXT', - typeClass: 'controlledVocabulary', - watermark: '', - description: 'The country or nation that the Dataset is about.', - multiple: false, - isControlledVocabulary: true, - displayFormat: '#VALUE, ', - isRequired: false, - displayOrder: 1, - displayOnCreate: true, - controlledVocabularyValues: [ - 'Afghanistan', - 'Albania', - 'Algeria', - 'American Samoa', - 'Andorra', - 'Angola', - 'Anguilla', - 'Antarctica', - 'Antigua and Barbuda', - 'Argentina', - 'Armenia', - 'Aruba', - 'Australia', - 'Austria', - 'Azerbaijan', - 'Bahamas', - 'Bahrain', - 'Bangladesh', - 'Barbados', - 'Belarus', - 'Belgium', - 'Belize', - 'Benin', - 'Bermuda', - 'Bhutan', - 'Bolivia, Plurinational State of', - 'Bonaire, Sint Eustatius and Saba', - 'Bosnia and Herzegovina', - 'Botswana', - 'Bouvet Island', - 'Brazil', - 'British Indian Ocean Territory', - 'Brunei Darussalam', - 'Bulgaria', - 'Burkina Faso', - 'Burundi', - 'Cambodia', - 'Cameroon', - 'Canada', - 'Cape Verde', - 'Cayman Islands', - 'Central African Republic', - 'Chad', - 'Chile', - 'China', - 'Christmas Island', - 'Cocos (Keeling) Islands', - 'Colombia', - 'Comoros', - 'Congo', - 'Congo, the Democratic Republic of the', - 'Cook Islands', - 'Costa Rica', - 'Croatia', - 'Cuba', - 'Curaçao', - 'Cyprus', - 'Czech Republic', - "Côte d'Ivoire", - 'Denmark', - 'Djibouti', - 'Dominica', - 'Dominican Republic', - 'Ecuador', - 'Egypt', - 'El Salvador', - 'Equatorial Guinea', - 'Eritrea', - 'Estonia', - 'Ethiopia', - 'Falkland Islands (Malvinas)', - 'Faroe Islands', - 'Fiji', - 'Finland', - 'France', - 'French Guiana', - 'French Polynesia', - 'French Southern Territories', - 'Gabon', - 'Gambia', - 'Georgia', - 'Germany', - 'Ghana', - 'Gibraltar', - 'Greece', - 'Greenland', - 'Grenada', - 'Guadeloupe', - 'Guam', - 'Guatemala', - 'Guernsey', - 'Guinea', - 'Guinea-Bissau', - 'Guyana', - 'Haiti', - 'Heard Island and Mcdonald Islands', - 'Holy See (Vatican City State)', - 'Honduras', - 'Hong Kong', - 'Hungary', - 'Iceland', - 'India', - 'Indonesia', - 'Iran, Islamic Republic of', - 'Iraq', - 'Ireland', - 'Isle of Man', - 'Israel', - 'Italy', - 'Jamaica', - 'Japan', - 'Jersey', - 'Jordan', - 'Kazakhstan', - 'Kenya', - 'Kiribati', - "Korea, Democratic People's Republic of", - 'Korea, Republic of', - 'Kuwait', - 'Kyrgyzstan', - "Lao People's Democratic Republic", - 'Latvia', - 'Lebanon', - 'Lesotho', - 'Liberia', - 'Libya', - 'Liechtenstein', - 'Lithuania', - 'Luxembourg', - 'Macao', - 'Macedonia, the Former Yugoslav Republic of', - 'Madagascar', - 'Malawi', - 'Malaysia', - 'Maldives', - 'Mali', - 'Malta', - 'Marshall Islands', - 'Martinique', - 'Mauritania', - 'Mauritius', - 'Mayotte', - 'Mexico', - 'Micronesia, Federated States of', - 'Moldova, Republic of', - 'Monaco', - 'Mongolia', - 'Montenegro', - 'Montserrat', - 'Morocco', - 'Mozambique', - 'Myanmar', - 'Namibia', - 'Nauru', - 'Nepal', - 'Netherlands', - 'New Caledonia', - 'New Zealand', - 'Nicaragua', - 'Niger', - 'Nigeria', - 'Niue', - 'Norfolk Island', - 'Northern Mariana Islands', - 'Norway', - 'Oman', - 'Pakistan', - 'Palau', - 'Palestine, State of', - 'Panama', - 'Papua New Guinea', - 'Paraguay', - 'Peru', - 'Philippines', - 'Pitcairn', - 'Poland', - 'Portugal', - 'Puerto Rico', - 'Qatar', - 'Romania', - 'Russian Federation', - 'Rwanda', - 'Réunion', - 'Saint Barthélemy', - 'Saint Helena, Ascension and Tristan da Cunha', - 'Saint Kitts and Nevis', - 'Saint Lucia', - 'Saint Martin (French part)', - 'Saint Pierre and Miquelon', - 'Saint Vincent and the Grenadines', - 'Samoa', - 'San Marino', - 'Sao Tome and Principe', - 'Saudi Arabia', - 'Senegal', - 'Serbia', - 'Seychelles', - 'Sierra Leone', - 'Singapore', - 'Sint Maarten (Dutch part)', - 'Slovakia', - 'Slovenia', - 'Solomon Islands', - 'Somalia', - 'South Africa', - 'South Georgia and the South Sandwich Islands', - 'South Sudan', - 'Spain', - 'Sri Lanka', - 'Sudan', - 'Suriname', - 'Svalbard and Jan Mayen', - 'Swaziland', - 'Sweden', - 'Switzerland', - 'Syrian Arab Republic', - 'Taiwan, Province of China', - 'Tajikistan', - 'Tanzania, United Republic of', - 'Thailand', - 'Timor-Leste', - 'Togo', - 'Tokelau', - 'Tonga', - 'Trinidad and Tobago', - 'Tunisia', - 'Turkey', - 'Turkmenistan', - 'Turks and Caicos Islands', - 'Tuvalu', - 'Uganda', - 'Ukraine', - 'United Arab Emirates', - 'United Kingdom', - 'United States', - 'United States Minor Outlying Islands', - 'Uruguay', - 'Uzbekistan', - 'Vanuatu', - 'Venezuela, Bolivarian Republic of', - 'Viet Nam', - 'Virgin Islands, British', - 'Virgin Islands, U.S.', - 'Wallis and Futuna', - 'Western Sahara', - 'Yemen', - 'Zambia', - 'Zimbabwe', - 'Åland Islands' - ] - }, - state: { - name: 'state', - displayName: 'Geographic Coverage State / Province', - title: 'State / Province', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: - 'The state or province that the Dataset is about. Use GeoNames for correct spelling and avoid abbreviations.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '#VALUE, ', - isRequired: false, - displayOnCreate: true, - displayOrder: 2 - }, - city: { - name: 'city', - displayName: 'Geographic Coverage City', - title: 'City', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: - 'The name of the city that the Dataset is about. Use GeoNames for correct spelling and avoid abbreviations.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '#VALUE, ', - isRequired: false, - displayOnCreate: true, - displayOrder: 3 - }, - otherGeographicCoverage: { - name: 'otherGeographicCoverage', - displayName: 'Geographic Coverage Other', - title: 'Other', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: 'Other information on the geographic coverage of the data.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '#VALUE, ', - isRequired: false, - displayOnCreate: true, - displayOrder: 4 - } - } - }, - geographicUnit: { - name: 'geographicUnit', - displayName: 'Geographic Unit', - title: 'Geographic Unit', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: - 'Lowest level of geographic aggregation covered by the Dataset, e.g., village, county, region.', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 5 - }, - geographicBoundingBox: { - name: 'geographicBoundingBox', - displayName: 'Geographic Bounding Box', - title: 'Geographic Bounding Box', - type: 'NONE', - typeClass: 'compound', - watermark: '', - description: - "The fundamental geometric description for any Dataset that models geography is the geographic bounding box. It describes the minimum box, defined by west and east longitudes and north and south latitudes, which includes the largest geographic extent of the Dataset's geographic coverage. This element is used in the first pass of a coordinate-based search. Inclusion of this element in the codebook is recommended, but is required if the bound polygon box is included. ", - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 6, - childMetadataFields: { - westLongitude: { - name: 'westLongitude', - displayName: 'Geographic Bounding Box Westernmost (Left) Longitude', - title: 'Westernmost (Left) Longitude', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: - 'Westernmost coordinate delimiting the geographic extent of the Dataset. A valid range of values, expressed in decimal degrees, is -180,0 <= West Bounding Longitude Value <= 180,0.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 7 - }, - eastLongitude: { - name: 'eastLongitude', - displayName: 'Geographic Bounding Box Easternmost (Right) Longitude', - title: 'Easternmost (Right) Longitude', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: - 'Easternmost coordinate delimiting the geographic extent of the Dataset. A valid range of values, expressed in decimal degrees, is -180,0 <= East Bounding Longitude Value <= 180,0.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 8 - }, - northLatitude: { - name: 'northLatitude', - displayName: 'Geographic Bounding Box Northernmost (Top) Latitude', - title: 'Northernmost (Top) Latitude', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: - 'Northernmost coordinate delimiting the geographic extent of the Dataset. A valid range of values, expressed in decimal degrees, is -90,0 <= North Bounding Latitude Value <= 90,0.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 9 - }, - southLatitude: { - name: 'southLatitude', - displayName: 'Geographic Bounding Box Southernmost (Bottom) Latitude', - title: 'Southernmost (Bottom) Latitude', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: - 'Southernmost coordinate delimiting the geographic extent of the Dataset. A valid range of values, expressed in decimal degrees, is -90,0 <= South Bounding Latitude Value <= 90,0.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 10 - } - } - } - } - }, - { - id: 4, - name: 'astrophysics', - displayName: 'Astronomy and Astrophysics Metadata', - displayOnCreate: false, - metadataFields: { - astroType: { - name: 'astroType', - displayName: 'Type', - title: 'Type', - type: 'TEXT', - typeClass: 'controlledVocabulary', - watermark: '', - description: 'The nature or genre of the content of the files in the dataset.', - multiple: true, - isControlledVocabulary: true, - displayFormat: '', - isRequired: false, - displayOrder: 0, - displayOnCreate: true, - controlledVocabularyValues: [ - 'Image', - 'Mosaic', - 'EventList', - 'Spectrum', - 'Cube', - 'Table', - 'Catalog', - 'LightCurve', - 'Simulation', - 'Figure', - 'Artwork', - 'Animation', - 'PrettyPicture', - 'Documentation', - 'Other', - 'Library', - 'Press Release', - 'Facsimile', - 'Historical', - 'Observation', - 'Object', - 'Value', - 'ValuePair', - 'Survey' - ] - }, - astroFacility: { - name: 'astroFacility', - displayName: 'Facility', - title: 'Facility', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: 'The observatory or facility where the data was obtained. ', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 1 - }, - astroInstrument: { - name: 'astroInstrument', - displayName: 'Instrument', - title: 'Instrument', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: 'The instrument used to collect the data.', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 2 - }, - astroObject: { - name: 'astroObject', - displayName: 'Object', - title: 'Object', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: - 'Astronomical Objects represented in the data (Given as SIMBAD recognizable names preferred).', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 3 - }, - 'resolution.Spatial': { - name: 'resolution.Spatial', - displayName: 'Spatial Resolution', - title: 'Spatial Resolution', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: - 'The spatial (angular) resolution that is typical of the observations, in decimal degrees.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 4 - }, - 'resolution.Spectral': { - name: 'resolution.Spectral', - displayName: 'Spectral Resolution', - title: 'Spectral Resolution', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: - 'The spectral resolution that is typical of the observations, given as the ratio \\u03bb/\\u0394\\u03bb.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 5 - }, - 'resolution.Temporal': { - name: 'resolution.Temporal', - displayName: 'Time Resolution', - title: 'Time Resolution', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: - 'The temporal resolution that is typical of the observations, given in seconds.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 6 - }, - 'coverage.Spectral.Bandpass': { - name: 'coverage.Spectral.Bandpass', - displayName: 'Bandpass', - title: 'Bandpass', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: 'Conventional bandpass name', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 7 - }, - 'coverage.Spectral.CentralWavelength': { - name: 'coverage.Spectral.CentralWavelength', - displayName: 'Central Wavelength (m)', - title: 'Central Wavelength (m)', - type: 'FLOAT', - typeClass: 'primitive', - watermark: 'Enter a floating-point number.', - description: 'The central wavelength of the spectral bandpass, in meters.', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 8 - }, - 'coverage.Spectral.Wavelength': { - name: 'coverage.Spectral.Wavelength', - displayName: 'Wavelength Range', - title: 'Wavelength Range', - type: 'NONE', - typeClass: 'compound', - watermark: 'Enter a floating-point number.', - description: 'The minimum and maximum wavelength of the spectral bandpass.', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 9, - childMetadataFields: { - 'coverage.Spectral.MinimumWavelength': { - name: 'coverage.Spectral.MinimumWavelength', - displayName: 'Wavelength Range Minimum (m)', - title: 'Minimum (m)', - type: 'FLOAT', - typeClass: 'primitive', - watermark: 'Enter a floating-point number.', - description: 'The minimum wavelength of the spectral bandpass, in meters.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 10 - }, - 'coverage.Spectral.MaximumWavelength': { - name: 'coverage.Spectral.MaximumWavelength', - displayName: 'Wavelength Range Maximum (m)', - title: 'Maximum (m)', - type: 'FLOAT', - typeClass: 'primitive', - watermark: 'Enter a floating-point number.', - description: 'The maximum wavelength of the spectral bandpass, in meters.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 11 - } - } - }, - 'coverage.Temporal': { - name: 'coverage.Temporal', - displayName: 'Dataset Date Range', - title: 'Dataset Date Range', - type: 'NONE', - typeClass: 'compound', - watermark: '', - description: ' Time period covered by the data.', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 12, - childMetadataFields: { - 'coverage.Temporal.StartTime': { - name: 'coverage.Temporal.StartTime', - displayName: 'Dataset Date Range Start', - title: 'Start', - type: 'DATE', - typeClass: 'primitive', - watermark: 'YYYY-MM-DD', - description: 'Dataset Start Date', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 13 - }, - 'coverage.Temporal.StopTime': { - name: 'coverage.Temporal.StopTime', - displayName: 'Dataset Date Range End', - title: 'End', - type: 'DATE', - typeClass: 'primitive', - watermark: 'YYYY-MM-DD', - description: 'Dataset End Date', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 14 - } - } - }, - 'coverage.Spatial': { - name: 'coverage.Spatial', - displayName: 'Sky Coverage', - title: 'Sky Coverage', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: 'The sky coverage of the data object.', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 15 - }, - 'coverage.Depth': { - name: 'coverage.Depth', - displayName: 'Depth Coverage', - title: 'Depth Coverage', - type: 'FLOAT', - typeClass: 'primitive', - watermark: 'Enter a floating-point number.', - description: 'The (typical) depth coverage, or sensitivity, of the data object in Jy.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 16 - }, - 'coverage.ObjectDensity': { - name: 'coverage.ObjectDensity', - displayName: 'Object Density', - title: 'Object Density', - type: 'FLOAT', - typeClass: 'primitive', - watermark: 'Enter a floating-point number.', - description: - 'The (typical) density of objects, catalog entries, telescope pointings, etc., on the sky, in number per square degree.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 17 - }, - 'coverage.ObjectCount': { - name: 'coverage.ObjectCount', - displayName: 'Object Count', - title: 'Object Count', - type: 'INT', - typeClass: 'primitive', - watermark: 'Enter an integer.', - description: 'The total number of objects, catalog entries, etc., in the data object.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 18 - }, - 'coverage.SkyFraction': { - name: 'coverage.SkyFraction', - displayName: 'Fraction of Sky', - title: 'Fraction of Sky', - type: 'FLOAT', - typeClass: 'primitive', - watermark: 'Enter a floating-point number.', - description: - 'The fraction of the sky represented in the observations, ranging from 0 to 1.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 19 - }, - 'coverage.Polarization': { - name: 'coverage.Polarization', - displayName: 'Polarization', - title: 'Polarization', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: 'The polarization coverage', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 20 - }, - redshiftType: { - name: 'redshiftType', - displayName: 'RedshiftType', - title: 'RedshiftType', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: - 'RedshiftType string C "Redshift"; or "Optical" or "Radio" definitions of Doppler velocity used in the data object.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 21 - }, - 'resolution.Redshift': { - name: 'resolution.Redshift', - displayName: 'Redshift Resolution', - title: 'Redshift Resolution', - type: 'FLOAT', - typeClass: 'primitive', - watermark: 'Enter a floating-point number.', - description: - 'The resolution in redshift (unitless) or Doppler velocity (km/s) in the data object.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 22 - }, - 'coverage.RedshiftValue': { - name: 'coverage.RedshiftValue', - displayName: 'Redshift Value', - title: 'Redshift Value', - type: 'FLOAT', - typeClass: 'compound', - watermark: 'Enter a floating-point number.', - description: - 'The value of the redshift (unitless) or Doppler velocity (km/s in the data object.', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 23, - childMetadataFields: { - 'coverage.Redshift.MinimumValue': { - name: 'coverage.Redshift.MinimumValue', - displayName: 'Redshift Value Minimum', - title: 'Minimum', - type: 'FLOAT', - typeClass: 'primitive', - watermark: 'Enter a floating-point number.', - description: - 'The minimum value of the redshift (unitless) or Doppler velocity (km/s in the data object.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 24 - }, - 'coverage.Redshift.MaximumValue': { - name: 'coverage.Redshift.MaximumValue', - displayName: 'Redshift Value Maximum', - title: 'Maximum', - type: 'FLOAT', - typeClass: 'primitive', - watermark: 'Enter a floating-point number.', - description: - 'The maximum value of the redshift (unitless) or Doppler velocity (km/s in the data object.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 25 - } - } - } - } - }, - { - id: 5, - name: 'biomedical', - displayName: 'Life Sciences Metadata', - displayOnCreate: false, - metadataFields: { - studyDesignType: { - name: 'studyDesignType', - displayName: 'Design Type', - title: 'Design Type', - type: 'TEXT', - typeClass: 'controlledVocabulary', - watermark: '', - description: 'Design types that are based on the overall experimental design.', - multiple: true, - isControlledVocabulary: true, - displayFormat: '', - isRequired: false, - displayOrder: 0, - displayOnCreate: true, - controlledVocabularyValues: [ - 'Case Control', - 'Cross Sectional', - 'Cohort Study', - 'Nested Case Control Design', - 'Not Specified', - 'Parallel Group Design', - 'Perturbation Design', - 'Randomized Controlled Trial', - 'Technological Design', - 'Other' - ] - }, - studyOtherDesignType: { - name: 'studyOtherDesignType', - displayName: 'Other Design Type', - title: 'Other Design Type', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: - 'If Other was selected in Design Type, list any other design types that were used in this Dataset.', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 1 - }, - studyFactorType: { - name: 'studyFactorType', - displayName: 'Factor Type', - title: 'Factor Type', - type: 'TEXT', - typeClass: 'controlledVocabulary', - watermark: '', - description: 'Factors used in the Dataset.', - multiple: true, - isControlledVocabulary: true, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 2, - controlledVocabularyValues: [ - 'Age', - 'Biomarkers', - 'Cell Surface Markers', - 'Cell Type/Cell Line', - 'Developmental Stage', - 'Disease State', - 'Drug Susceptibility', - 'Extract Molecule', - 'Genetic Characteristics', - 'Immunoprecipitation Antibody', - 'Organism', - 'Passages', - 'Platform', - 'Sex', - 'Strain', - 'Time Point', - 'Tissue Type', - 'Treatment Compound', - 'Treatment Type', - 'Other' - ] - }, - studyOtherFactorType: { - name: 'studyOtherFactorType', - displayName: 'Other Factor Type', - title: 'Other Factor Type', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: - 'If Other was selected in Factor Type, list any other factor types that were used in this Dataset.', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 3 - }, - studyAssayOrganism: { - name: 'studyAssayOrganism', - displayName: 'Organism', - title: 'Organism', - type: 'TEXT', - typeClass: 'controlledVocabulary', - watermark: '', - description: - 'The taxonomic name of the organism used in the Dataset or from which the starting biological material derives.', - multiple: true, - isControlledVocabulary: true, - displayFormat: '', - isRequired: false, - displayOrder: 4, - displayOnCreate: true, - controlledVocabularyValues: [ - 'Arabidopsis thaliana', - 'Bos taurus', - 'Caenorhabditis elegans', - 'Chlamydomonas reinhardtii', - 'Danio rerio (zebrafish)', - 'Dictyostelium discoideum', - 'Drosophila melanogaster', - 'Escherichia coli', - 'Hepatitis C virus', - 'Homo sapiens', - 'Mus musculus', - 'Mycobacterium africanum', - 'Mycobacterium canetti', - 'Mycobacterium tuberculosis', - 'Mycoplasma pneumoniae', - 'Oryza sativa', - 'Plasmodium falciparum', - 'Pneumocystis carinii', - 'Rattus norvegicus', - "Saccharomyces cerevisiae (brewer's yeast)", - 'Schizosaccharomyces pombe', - 'Takifugu rubripes', - 'Xenopus laevis', - 'Zea mays', - 'Other' - ] - }, - studyAssayOtherOrganism: { - name: 'studyAssayOtherOrganism', - displayName: 'Other Organism', - title: 'Other Organism', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: - 'If Other was selected in Organism, list any other organisms that were used in this Dataset. Terms from the NCBI Taxonomy are recommended.', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 5 - }, - studyAssayMeasurementType: { - name: 'studyAssayMeasurementType', - displayName: 'Measurement Type', - title: 'Measurement Type', - type: 'TEXT', - typeClass: 'controlledVocabulary', - watermark: '', - description: - 'A term to qualify the endpoint, or what is being measured (e.g. gene expression profiling; protein identification).', - multiple: true, - isControlledVocabulary: true, - displayFormat: '', - isRequired: false, - displayOrder: 6, - displayOnCreate: true, - controlledVocabularyValues: [ - 'cell sorting', - 'clinical chemistry analysis', - 'copy number variation profiling', - 'DNA methylation profiling', - 'DNA methylation profiling (Bisulfite-Seq)', - 'DNA methylation profiling (MeDIP-Seq)', - 'drug susceptibility', - 'environmental gene survey', - 'genome sequencing', - 'hematology', - 'histology', - 'Histone Modification (ChIP-Seq)', - 'loss of heterozygosity profiling', - 'metabolite profiling', - 'metagenome sequencing', - 'protein expression profiling', - 'protein identification', - 'protein-DNA binding site identification', - 'protein-protein interaction detection', - 'protein-RNA binding (RIP-Seq)', - 'SNP analysis', - 'targeted sequencing', - 'transcription factor binding (ChIP-Seq)', - 'transcription factor binding site identification', - 'transcription profiling', - 'transcription profiling (Microarray)', - 'transcription profiling (RNA-Seq)', - 'TRAP translational profiling', - 'Other' - ] - }, - studyAssayOtherMeasurmentType: { - name: 'studyAssayOtherMeasurmentType', - displayName: 'Other Measurement Type', - title: 'Other Measurement Type', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: - 'If Other was selected in Measurement Type, list any other measurement types that were used. Terms from NCBO Bioportal are recommended.', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 7 - }, - studyAssayTechnologyType: { - name: 'studyAssayTechnologyType', - displayName: 'Technology Type', - title: 'Technology Type', - type: 'TEXT', - typeClass: 'controlledVocabulary', + producer: { + name: 'producer', + displayName: 'Producer', + title: 'Producer', + type: 'NONE', watermark: '', description: - 'A term to identify the technology used to perform the measurement (e.g. DNA microarray; mass spectrometry).', + 'The entity, such a person or organization, managing the finances or other administrative processes involved in the creation of the Dataset', multiple: true, - isControlledVocabulary: true, + isControlledVocabulary: false, displayFormat: '', isRequired: false, - displayOrder: 8, - displayOnCreate: true, - controlledVocabularyValues: [ - 'culture based drug susceptibility testing, single concentration', - 'culture based drug susceptibility testing, two concentrations', - 'culture based drug susceptibility testing, three or more concentrations (minimium inhibitory concentration measurement)', - 'DNA microarray', - 'flow cytometry', - 'gel electrophoresis', - 'mass spectrometry', - 'NMR spectroscopy', - 'nucleotide sequencing', - 'protein microarray', - 'real time PCR', - 'no technology required', - 'Other' - ] + displayOrder: 36, + typeClass: 'compound', + displayOnCreate: false, + childMetadataFields: { + producerName: { + name: 'producerName', + displayName: 'Producer Name', + title: 'Name', + type: 'TEXT', + watermark: '1) FamilyName, GivenName or 2) Organization', + description: + "The name of the entity, e.g. the person's name or the name of an organization", + multiple: false, + isControlledVocabulary: false, + displayFormat: '#VALUE', + isRequired: true, + displayOrder: 37, + typeClass: 'primitive', + displayOnCreate: false + }, + producerAffiliation: { + name: 'producerAffiliation', + displayName: 'Producer Affiliation', + title: 'Affiliation', + type: 'TEXT', + watermark: 'Organization XYZ', + description: + "The name of the entity affiliated with the producer, e.g. an organization's name", + multiple: false, + isControlledVocabulary: false, + displayFormat: '(#VALUE)', + isRequired: false, + displayOrder: 38, + typeClass: 'primitive', + displayOnCreate: false + }, + producerAbbreviation: { + name: 'producerAbbreviation', + displayName: 'Producer Abbreviated Name', + title: 'Abbreviated Name', + type: 'TEXT', + watermark: '', + description: "The producer's abbreviated name (e.g. IQSS, ICPSR)", + multiple: false, + isControlledVocabulary: false, + displayFormat: '(#VALUE)', + isRequired: false, + displayOrder: 39, + typeClass: 'primitive', + displayOnCreate: false + }, + producerURL: { + name: 'producerURL', + displayName: 'Producer URL', + title: 'URL', + type: 'URL', + watermark: 'https://', + description: "The URL of the producer's website", + multiple: false, + isControlledVocabulary: false, + displayFormat: '#VALUE', + isRequired: false, + displayOrder: 40, + typeClass: 'primitive', + displayOnCreate: false + }, + producerLogoURL: { + name: 'producerLogoURL', + displayName: 'Producer Logo URL', + title: 'Logo URL', + type: 'URL', + watermark: 'https://', + description: "The URL of the producer's logo", + multiple: false, + isControlledVocabulary: false, + displayFormat: '
', + isRequired: false, + displayOrder: 41, + typeClass: 'primitive', + displayOnCreate: false + } + } }, - studyAssayOtherTechnologyType: { - name: 'studyAssayOtherTechnologyType', - displayName: 'Other Technology Type', - title: 'Other Technology Type', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', + productionDate: { + name: 'productionDate', + displayName: 'Production Date', + title: 'Production Date', + type: 'DATE', + watermark: 'YYYY-MM-DD', description: - 'If Other was selected in Technology Type, list any other technology types that were used in this Dataset.', - multiple: true, + 'The date when the data were produced (not distributed, published, or archived)', + multiple: false, isControlledVocabulary: false, displayFormat: '', isRequired: false, - displayOnCreate: true, - displayOrder: 9 + displayOrder: 42, + typeClass: 'primitive', + displayOnCreate: false }, - studyAssayPlatform: { - name: 'studyAssayPlatform', - displayName: 'Technology Platform', - title: 'Technology Platform', + productionPlace: { + name: 'productionPlace', + displayName: 'Production Location', + title: 'Production Location', type: 'TEXT', - typeClass: 'controlledVocabulary', watermark: '', description: - 'The manufacturer and name of the technology platform used in the assay (e.g. Bruker AVANCE).', + 'The location where the data and any related materials were produced or collected', multiple: true, - isControlledVocabulary: true, + isControlledVocabulary: false, displayFormat: '', isRequired: false, - displayOnCreate: true, - displayOrder: 10, - controlledVocabularyValues: [ - '210-MS GC Ion Trap (Varian)', - '220-MS GC Ion Trap (Varian)', - '225-MS GC Ion Trap (Varian)', - '240-MS GC Ion Trap (Varian)', - '300-MS quadrupole GC/MS (Varian)', - '320-MS LC/MS (Varian)', - '325-MS LC/MS (Varian)', - '320-MS GC/MS (Varian)', - '500-MS LC/MS (Varian)', - '800D (Jeol)', - '910-MS TQ-FT (Varian)', - '920-MS TQ-FT (Varian)', - '3100 Mass Detector (Waters)', - '6110 Quadrupole LC/MS (Agilent)', - '6120 Quadrupole LC/MS (Agilent)', - '6130 Quadrupole LC/MS (Agilent)', - '6140 Quadrupole LC/MS (Agilent)', - '6310 Ion Trap LC/MS (Agilent)', - '6320 Ion Trap LC/MS (Agilent)', - '6330 Ion Trap LC/MS (Agilent)', - '6340 Ion Trap LC/MS (Agilent)', - '6410 Triple Quadrupole LC/MS (Agilent)', - '6430 Triple Quadrupole LC/MS (Agilent)', - '6460 Triple Quadrupole LC/MS (Agilent)', - '6490 Triple Quadrupole LC/MS (Agilent)', - '6530 Q-TOF LC/MS (Agilent)', - '6540 Q-TOF LC/MS (Agilent)', - '6210 TOF LC/MS (Agilent)', - '6220 TOF LC/MS (Agilent)', - '6230 TOF LC/MS (Agilent)', - '7000B Triple Quadrupole GC/MS (Agilent)', - 'AccuTO DART (Jeol)', - 'AccuTOF GC (Jeol)', - 'AccuTOF LC (Jeol)', - 'ACQUITY SQD (Waters)', - 'ACQUITY TQD (Waters)', - 'Agilent', - 'Agilent 5975E GC/MSD (Agilent)', - 'Agilent 5975T LTM GC/MSD (Agilent)', - '5975C Series GC/MSD (Agilent)', - 'Affymetrix', - 'amaZon ETD ESI Ion Trap (Bruker)', - 'amaZon X ESI Ion Trap (Bruker)', - 'apex-ultra hybrid Qq-FTMS (Bruker)', - 'API 2000 (AB Sciex)', - 'API 3200 (AB Sciex)', - 'API 3200 QTRAP (AB Sciex)', - 'API 4000 (AB Sciex)', - 'API 4000 QTRAP (AB Sciex)', - 'API 5000 (AB Sciex)', - 'API 5500 (AB Sciex)', - 'API 5500 QTRAP (AB Sciex)', - 'Applied Biosystems Group (ABI)', - 'AQI Biosciences', - 'Atmospheric Pressure GC (Waters)', - 'autoflex III MALDI-TOF MS (Bruker)', - 'autoflex speed(Bruker)', - 'AutoSpec Premier (Waters)', - 'AXIMA Mega TOF (Shimadzu)', - 'AXIMA Performance MALDI TOF/TOF (Shimadzu)', - 'A-10 Analyzer (Apogee)', - 'A-40-MiniFCM (Apogee)', - 'Bactiflow (Chemunex SA)', - 'Base4innovation', - 'BD BACTEC MGIT 320', - 'BD BACTEC MGIT 960', - 'BD Radiometric BACTEC 460TB', - 'BioNanomatrix', - 'Cell Lab Quanta SC (Becman Coulter)', - 'Clarus 560 D GC/MS (PerkinElmer)', - 'Clarus 560 S GC/MS (PerkinElmer)', - 'Clarus 600 GC/MS (PerkinElmer)', - 'Complete Genomics', - 'Cyan (Dako Cytomation)', - 'CyFlow ML (Partec)', - 'Cyow SL (Partec)', - 'CyFlow SL3 (Partec)', - 'CytoBuoy (Cyto Buoy Inc)', - 'CytoSence (Cyto Buoy Inc)', - 'CytoSub (Cyto Buoy Inc)', - 'Danaher', - 'DFS (Thermo Scientific)', - 'Exactive(Thermo Scientific)', - 'FACS Canto (Becton Dickinson)', - 'FACS Canto2 (Becton Dickinson)', - 'FACS Scan (Becton Dickinson)', - 'FC 500 (Becman Coulter)', - 'GCmate II GC/MS (Jeol)', - 'GCMS-QP2010 Plus (Shimadzu)', - 'GCMS-QP2010S Plus (Shimadzu)', - 'GCT Premier (Waters)', - 'GENEQ', - 'Genome Corp.', - 'GenoVoxx', - 'GnuBio', - 'Guava EasyCyte Mini (Millipore)', - 'Guava EasyCyte Plus (Millipore)', - 'Guava Personal Cell Analysis (Millipore)', - 'Guava Personal Cell Analysis-96 (Millipore)', - 'Helicos BioSciences', - 'Illumina', - 'Indirect proportion method on LJ medium', - 'Indirect proportion method on Middlebrook Agar 7H9', - 'Indirect proportion method on Middlebrook Agar 7H10', - 'Indirect proportion method on Middlebrook Agar 7H11', - 'inFlux Analyzer (Cytopeia)', - 'Intelligent Bio-Systems', - 'ITQ 700 (Thermo Scientific)', - 'ITQ 900 (Thermo Scientific)', - 'ITQ 1100 (Thermo Scientific)', - 'JMS-53000 SpiralTOF (Jeol)', - 'LaserGen', - 'LCMS-2020 (Shimadzu)', - 'LCMS-2010EV (Shimadzu)', - 'LCMS-IT-TOF (Shimadzu)', - 'Li-Cor', - 'Life Tech', - 'LightSpeed Genomics', - 'LCT Premier XE (Waters)', - 'LCQ Deca XP MAX (Thermo Scientific)', - 'LCQ Fleet (Thermo Scientific)', - 'LXQ (Thermo Scientific)', - 'LTQ Classic (Thermo Scientific)', - 'LTQ XL (Thermo Scientific)', - 'LTQ Velos (Thermo Scientific)', - 'LTQ Orbitrap Classic (Thermo Scientific)', - 'LTQ Orbitrap XL (Thermo Scientific)', - 'LTQ Orbitrap Discovery (Thermo Scientific)', - 'LTQ Orbitrap Velos (Thermo Scientific)', - 'Luminex 100 (Luminex)', - 'Luminex 200 (Luminex)', - 'MACS Quant (Miltenyi)', - 'MALDI SYNAPT G2 HDMS (Waters)', - 'MALDI SYNAPT G2 MS (Waters)', - 'MALDI SYNAPT HDMS (Waters)', - 'MALDI SYNAPT MS (Waters)', - 'MALDI micro MX (Waters)', - 'maXis (Bruker)', - 'maXis G4 (Bruker)', - 'microflex LT MALDI-TOF MS (Bruker)', - 'microflex LRF MALDI-TOF MS (Bruker)', - 'microflex III MALDI-TOF MS (Bruker)', - 'micrOTOF II ESI TOF (Bruker)', - 'micrOTOF-Q II ESI-Qq-TOF (Bruker)', - 'microplate Alamar Blue (resazurin) colorimetric method', - 'Mstation (Jeol)', - 'MSQ Plus (Thermo Scientific)', - 'NABsys', - 'Nanophotonics Biosciences', - 'Network Biosystems', - 'Nimblegen', - 'Oxford Nanopore Technologies', - 'Pacific Biosciences', - 'Population Genetics Technologies', - 'Q1000GC UltraQuad (Jeol)', - 'Quattro micro API (Waters)', - 'Quattro micro GC (Waters)', - 'Quattro Premier XE (Waters)', - 'QSTAR (AB Sciex)', - 'Reveo', - 'Roche', - 'Seirad', - 'solariX hybrid Qq-FTMS (Bruker)', - 'Somacount (Bently Instruments)', - 'SomaScope (Bently Instruments)', - 'SYNAPT G2 HDMS (Waters)', - 'SYNAPT G2 MS (Waters)', - 'SYNAPT HDMS (Waters)', - 'SYNAPT MS (Waters)', - 'TripleTOF 5600 (AB Sciex)', - 'TSQ Quantum Ultra (Thermo Scientific)', - 'TSQ Quantum Access (Thermo Scientific)', - 'TSQ Quantum Access MAX (Thermo Scientific)', - 'TSQ Quantum Discovery MAX (Thermo Scientific)', - 'TSQ Quantum GC (Thermo Scientific)', - 'TSQ Quantum XLS (Thermo Scientific)', - 'TSQ Vantage (Thermo Scientific)', - 'ultrafleXtreme MALDI-TOF MS (Bruker)', - 'VisiGen Biotechnologies', - 'Xevo G2 QTOF (Waters)', - 'Xevo QTof MS (Waters)', - 'Xevo TQ MS (Waters)', - 'Xevo TQ-S (Waters)', - 'Other' - ] - }, - studyAssayOtherPlatform: { - name: 'studyAssayOtherPlatform', - displayName: 'Other Technology Platform', - title: 'Other Technology Platform', - type: 'TEXT', + displayOrder: 43, typeClass: 'primitive', + displayOnCreate: false + }, + contributor: { + name: 'contributor', + displayName: 'Contributor', + title: 'Contributor', + type: 'NONE', watermark: '', description: - 'If Other was selected in Technology Platform, list any other technology platforms that were used in this Dataset.', + 'The entity, such as a person or organization, responsible for collecting, managing, or otherwise contributing to the development of the Dataset', multiple: true, isControlledVocabulary: false, - displayFormat: '', + displayFormat: ':', isRequired: false, - displayOnCreate: true, - displayOrder: 11 + displayOrder: 44, + typeClass: 'compound', + displayOnCreate: false, + childMetadataFields: { + contributorType: { + name: 'contributorType', + displayName: 'Contributor Type', + title: 'Type', + type: 'TEXT', + watermark: '', + description: 'Indicates the type of contribution made to the dataset', + multiple: false, + isControlledVocabulary: true, + controlledVocabularyValues: [ + 'Data Collector', + 'Data Curator', + 'Data Manager', + 'Editor', + 'Funder', + 'Hosting Institution', + 'Project Leader', + 'Project Manager', + 'Project Member', + 'Related Person', + 'Researcher', + 'Research Group', + 'Rights Holder', + 'Sponsor', + 'Supervisor', + 'Work Package Leader', + 'Other' + ], + displayFormat: '#VALUE ', + isRequired: false, + displayOrder: 45, + typeClass: 'controlledVocabulary', + displayOnCreate: false + }, + contributorName: { + name: 'contributorName', + displayName: 'Contributor Name', + title: 'Name', + type: 'TEXT', + watermark: '1) FamilyName, GivenName or 2) Organization', + description: + "The name of the contributor, e.g. the person's name or the name of an organization", + multiple: false, + isControlledVocabulary: false, + displayFormat: '#VALUE', + isRequired: false, + displayOrder: 46, + typeClass: 'primitive', + displayOnCreate: false + } + } }, - studyAssayCellType: { - name: 'studyAssayCellType', - displayName: 'Cell Type', - title: 'Cell Type', - type: 'TEXT', - typeClass: 'primitive', + grantNumber: { + name: 'grantNumber', + displayName: 'Funding Information', + title: 'Funding Information', + type: 'NONE', watermark: '', - description: 'The name of the cell line from which the source or sample derives.', + description: "Information about the Dataset's financial support", multiple: true, isControlledVocabulary: false, - displayFormat: '', + displayFormat: ':', isRequired: false, - displayOnCreate: true, - displayOrder: 12 - } - } - }, - { - id: 6, - name: 'journal', - displayName: 'Journal Metadata', - displayOnCreate: false, - metadataFields: { - journalVolumeIssue: { - name: 'journalVolumeIssue', - displayName: 'Journal', - title: 'Journal', - type: 'NONE', + displayOrder: 47, typeClass: 'compound', + displayOnCreate: false, + childMetadataFields: { + grantNumberAgency: { + name: 'grantNumberAgency', + displayName: 'Funding Information Agency', + title: 'Agency', + type: 'TEXT', + watermark: 'Organization XYZ', + description: 'The agency that provided financial support for the Dataset', + multiple: false, + isControlledVocabulary: false, + displayFormat: '#VALUE', + isRequired: false, + displayOrder: 48, + typeClass: 'primitive', + displayOnCreate: false + }, + grantNumberValue: { + name: 'grantNumberValue', + displayName: 'Funding Information Identifier', + title: 'Identifier', + type: 'TEXT', + watermark: '', + description: + 'The grant identifier or contract identifier of the agency that provided financial support for the Dataset', + multiple: false, + isControlledVocabulary: false, + displayFormat: '#VALUE', + isRequired: false, + displayOrder: 49, + typeClass: 'primitive', + displayOnCreate: false + } + } + }, + distributor: { + name: 'distributor', + displayName: 'Distributor', + title: 'Distributor', + type: 'NONE', watermark: '', description: - 'Indicates the volume, issue and date of a journal, which this Dataset is associated with.', + 'The entity, such as a person or organization, designated to generate copies of the Dataset, including any editions or revisions', multiple: true, isControlledVocabulary: false, displayFormat: '', isRequired: false, - displayOrder: 0, - displayOnCreate: true, + displayOrder: 50, + typeClass: 'compound', + displayOnCreate: false, childMetadataFields: { - journalVolume: { - name: 'journalVolume', - displayName: 'Journal Volume', - title: 'Volume', + distributorName: { + name: 'distributorName', + displayName: 'Distributor Name', + title: 'Name', type: 'TEXT', + watermark: '1) FamilyName, GivenName or 2) Organization', + description: + "The name of the entity, e.g. the person's name or the name of an organization", + multiple: false, + isControlledVocabulary: false, + displayFormat: '#VALUE', + isRequired: false, + displayOrder: 51, typeClass: 'primitive', - watermark: '', + displayOnCreate: false + }, + distributorAffiliation: { + name: 'distributorAffiliation', + displayName: 'Distributor Affiliation', + title: 'Affiliation', + type: 'TEXT', + watermark: 'Organization XYZ', description: - 'The journal volume which this Dataset is associated with (e.g., Volume 4).', + "The name of the entity affiliated with the distributor, e.g. an organization's name", + multiple: false, + isControlledVocabulary: false, + displayFormat: '(#VALUE)', + isRequired: false, + displayOrder: 52, + typeClass: 'primitive', + displayOnCreate: false + }, + distributorAbbreviation: { + name: 'distributorAbbreviation', + displayName: 'Distributor Abbreviated Name', + title: 'Abbreviated Name', + type: 'TEXT', + watermark: '', + description: "The distributor's abbreviated name (e.g. IQSS, ICPSR)", multiple: false, isControlledVocabulary: false, - displayFormat: '', + displayFormat: '(#VALUE)', isRequired: false, - displayOnCreate: true, - displayOrder: 1 - }, - journalIssue: { - name: 'journalIssue', - displayName: 'Journal Issue', - title: 'Issue', - type: 'TEXT', + displayOrder: 53, typeClass: 'primitive', - watermark: '', - description: - 'The journal issue number which this Dataset is associated with (e.g., Number 2, Autumn).', + displayOnCreate: false + }, + distributorURL: { + name: 'distributorURL', + displayName: 'Distributor URL', + title: 'URL', + type: 'URL', + watermark: 'https://', + description: "The URL of the distributor's webpage", multiple: false, isControlledVocabulary: false, - displayFormat: '', + displayFormat: '#VALUE', isRequired: false, - displayOnCreate: true, - displayOrder: 2 - }, - journalPubDate: { - name: 'journalPubDate', - displayName: 'Journal Publication Date', - title: 'Publication Date', - type: 'DATE', + displayOrder: 54, typeClass: 'primitive', - watermark: 'YYYY or YYYY-MM or YYYY-MM-DD', + displayOnCreate: false + }, + distributorLogoURL: { + name: 'distributorLogoURL', + displayName: 'Distributor Logo URL', + title: 'Logo URL', + type: 'URL', + watermark: 'https://', description: - 'The publication date for this journal volume/issue, which this Dataset is associated with (e.g., 1999).', + "The URL of the distributor's logo image, used to show the image on the Dataset's page", multiple: false, isControlledVocabulary: false, - displayFormat: '', + displayFormat: '
', isRequired: false, - displayOnCreate: true, - displayOrder: 3 + displayOrder: 55, + typeClass: 'primitive', + displayOnCreate: false } } }, - journalArticleType: { - name: 'journalArticleType', - displayName: 'Type of Article', - title: 'Type of Article', - type: 'TEXT', - typeClass: 'controlledVocabulary', - watermark: '', + distributionDate: { + name: 'distributionDate', + displayName: 'Distribution Date', + title: 'Distribution Date', + type: 'DATE', + watermark: 'YYYY-MM-DD', description: - 'Indicates what kind of article this is, for example, a research article, a commentary, a book or product review, a case report, a calendar, etc (based on JATS). ', + 'The date when the Dataset was made available for distribution/presentation', multiple: false, - isControlledVocabulary: true, - displayFormat: '', - isRequired: false, - displayOrder: 4, - displayOnCreate: true, - controlledVocabularyValues: [ - 'abstract', - 'addendum', - 'announcement', - 'article-commentary', - 'book review', - 'books received', - 'brief report', - 'calendar', - 'case report', - 'collection', - 'correction', - 'data paper', - 'discussion', - 'dissertation', - 'editorial', - 'in brief', - 'introduction', - 'letter', - 'meeting report', - 'news', - 'obituary', - 'oration', - 'partial retraction', - 'product review', - 'rapid communication', - 'reply', - 'reprint', - 'research article', - 'retraction', - 'review article', - 'translation', - 'other' - ] - } - } - }, - { - id: 3, - name: 'socialscience', - displayName: 'Social Science and Humanities Metadata', - displayOnCreate: false, - metadataFields: { - unitOfAnalysis: { - name: 'unitOfAnalysis', - displayName: 'Unit of Analysis', - title: 'Unit of Analysis', - type: 'TEXTBOX', - typeClass: 'primitive', - watermark: '', - description: - "Basic unit of analysis or observation that this Dataset describes, such as individuals, families/households, groups, institutions/organizations, administrative units, and more. For information about the DDI's controlled vocabulary for this element, please refer to the DDI web page at http://www.ddialliance.org/controlled-vocabularies.", - multiple: true, isControlledVocabulary: false, displayFormat: '', isRequired: false, - displayOnCreate: true, - displayOrder: 0 - }, - universe: { - name: 'universe', - displayName: 'Universe', - title: 'Universe', - type: 'TEXTBOX', + displayOrder: 56, typeClass: 'primitive', - watermark: '', + displayOnCreate: false + }, + depositor: { + name: 'depositor', + displayName: 'Depositor', + title: 'Depositor', + type: 'TEXT', + watermark: '1) FamilyName, GivenName or 2) Organization', description: - 'Description of the population covered by the data in the file; the group of people or other elements that are the object of the study and to which the study results refer. Age, nationality, and residence commonly help to delineate a given universe, but any number of other factors may be used, such as age limits, sex, marital status, race, ethnic group, nationality, income, veteran status, criminal convictions, and more. The universe may consist of elements other than persons, such as housing units, court cases, deaths, countries, and so on. In general, it should be possible to tell from the description of the universe whether a given individual or element is a member of the population under study. Also known as the universe of interest, population of interest, and target population.', - multiple: true, + 'The entity, such as a person or organization, that deposited the Dataset in the repository', + multiple: false, isControlledVocabulary: false, displayFormat: '', isRequired: false, - displayOnCreate: true, - displayOrder: 1 - }, - timeMethod: { - name: 'timeMethod', - displayName: 'Time Method', - title: 'Time Method', - type: 'TEXT', + displayOrder: 57, typeClass: 'primitive', - watermark: '', - description: - 'The time method or time dimension of the data collection, such as panel, cross-sectional, trend, time- series, or other.', + displayOnCreate: false + }, + dateOfDeposit: { + name: 'dateOfDeposit', + displayName: 'Deposit Date', + title: 'Deposit Date', + type: 'DATE', + watermark: 'YYYY-MM-DD', + description: 'The date when the Dataset was deposited into the repository', multiple: false, isControlledVocabulary: false, displayFormat: '', isRequired: false, - displayOnCreate: true, - displayOrder: 2 - }, - dataCollector: { - name: 'dataCollector', - displayName: 'Data Collector', - title: 'Data Collector', - type: 'TEXT', + displayOrder: 58, typeClass: 'primitive', - watermark: 'FamilyName, GivenName or Organization', + displayOnCreate: false + }, + timePeriodCovered: { + name: 'timePeriodCovered', + displayName: 'Time Period', + title: 'Time Period', + type: 'NONE', + watermark: '', description: - 'Individual, agency or organization responsible for administering the questionnaire or interview or compiling the data.', - multiple: false, + 'The time period that the data refer to. Also known as span. This is the time period covered by the data, not the dates of coding, collecting data, or making documents machine-readable', + multiple: true, isControlledVocabulary: false, - displayFormat: '', + displayFormat: ';', isRequired: false, - displayOnCreate: true, - displayOrder: 3 + displayOrder: 59, + typeClass: 'compound', + displayOnCreate: false, + childMetadataFields: { + timePeriodCoveredStart: { + name: 'timePeriodCoveredStart', + displayName: 'Time Period Start Date', + title: 'Start Date', + type: 'DATE', + watermark: 'YYYY-MM-DD', + description: 'The start date of the time period that the data refer to', + multiple: false, + isControlledVocabulary: false, + displayFormat: '#NAME: #VALUE ', + isRequired: false, + displayOrder: 60, + typeClass: 'primitive', + displayOnCreate: false + }, + timePeriodCoveredEnd: { + name: 'timePeriodCoveredEnd', + displayName: 'Time Period End Date', + title: 'End Date', + type: 'DATE', + watermark: 'YYYY-MM-DD', + description: 'The end date of the time period that the data refer to', + multiple: false, + isControlledVocabulary: false, + displayFormat: '#NAME: #VALUE ', + isRequired: false, + displayOrder: 61, + typeClass: 'primitive', + displayOnCreate: false + } + } }, - collectorTraining: { - name: 'collectorTraining', - displayName: 'Collector Training', - title: 'Collector Training', - type: 'TEXT', - typeClass: 'primitive', + dateOfCollection: { + name: 'dateOfCollection', + displayName: 'Date of Collection', + title: 'Date of Collection', + type: 'NONE', watermark: '', - description: 'Type of training provided to the data collector', - multiple: false, + description: 'The dates when the data were collected or generated', + multiple: true, isControlledVocabulary: false, - displayFormat: '', + displayFormat: ';', isRequired: false, - displayOnCreate: true, - displayOrder: 4 + displayOrder: 62, + typeClass: 'compound', + displayOnCreate: false, + childMetadataFields: { + dateOfCollectionStart: { + name: 'dateOfCollectionStart', + displayName: 'Date of Collection Start Date', + title: 'Start Date', + type: 'DATE', + watermark: 'YYYY-MM-DD', + description: 'The date when the data collection started', + multiple: false, + isControlledVocabulary: false, + displayFormat: '#NAME: #VALUE ', + isRequired: false, + displayOrder: 63, + typeClass: 'primitive', + displayOnCreate: false + }, + dateOfCollectionEnd: { + name: 'dateOfCollectionEnd', + displayName: 'Date of Collection End Date', + title: 'End Date', + type: 'DATE', + watermark: 'YYYY-MM-DD', + description: 'The date when the data collection ended', + multiple: false, + isControlledVocabulary: false, + displayFormat: '#NAME: #VALUE ', + isRequired: false, + displayOrder: 64, + typeClass: 'primitive', + displayOnCreate: false + } + } }, - frequencyOfDataCollection: { - name: 'frequencyOfDataCollection', - displayName: 'Frequency', - title: 'Frequency', + kindOfData: { + name: 'kindOfData', + displayName: 'Data Type', + title: 'Data Type', type: 'TEXT', - typeClass: 'primitive', watermark: '', description: - 'If the data collected includes more than one point in time, indicate the frequency with which the data was collected; that is, monthly, quarterly, or other.', - multiple: false, + 'The type of data included in the files (e.g. survey data, clinical data, or machine-readable text)', + multiple: true, isControlledVocabulary: false, displayFormat: '', isRequired: false, - displayOnCreate: true, - displayOrder: 5 - }, - samplingProcedure: { - name: 'samplingProcedure', - displayName: 'Sampling Procedure', - title: 'Sampling Procedure', - type: 'TEXTBOX', + displayOrder: 65, typeClass: 'primitive', + displayOnCreate: false + }, + series: { + name: 'series', + displayName: 'Series', + title: 'Series', + type: 'NONE', watermark: '', - description: - 'Type of sample and sample design used to select the survey respondents to represent the population. May include reference to the target sample size and the sampling fraction.', - multiple: false, + description: 'Information about the dataset series to which the Dataset belong', + multiple: true, isControlledVocabulary: false, - displayFormat: '', + displayFormat: ':', isRequired: false, - displayOnCreate: true, - displayOrder: 6 + displayOrder: 66, + typeClass: 'compound', + displayOnCreate: false, + childMetadataFields: { + seriesName: { + name: 'seriesName', + displayName: 'Series Name', + title: 'Name', + type: 'TEXT', + watermark: '', + description: 'The name of the dataset series', + multiple: false, + isControlledVocabulary: false, + displayFormat: '#VALUE', + isRequired: false, + displayOrder: 67, + typeClass: 'primitive', + displayOnCreate: false + }, + seriesInformation: { + name: 'seriesInformation', + displayName: 'Series Information', + title: 'Information', + type: 'TEXTBOX', + watermark: '', + description: + 'Can include 1) a history of the series and 2) a summary of features that apply to the series', + multiple: false, + isControlledVocabulary: false, + displayFormat: '#VALUE', + isRequired: false, + displayOrder: 68, + typeClass: 'primitive', + displayOnCreate: false + } + } }, - targetSampleSize: { - name: 'targetSampleSize', - displayName: 'Target Sample Size', - title: 'Target Sample Size', + software: { + name: 'software', + displayName: 'Software', + title: 'Software', type: 'NONE', - typeClass: 'compound', watermark: '', - description: - 'Specific information regarding the target sample size, actual sample size, and the formula used to determine this.', - multiple: false, + description: 'Information about the software used to generate the Dataset', + multiple: true, isControlledVocabulary: false, - displayFormat: '', + displayFormat: ',', isRequired: false, - displayOnCreate: true, - displayOrder: 7, + displayOrder: 69, + typeClass: 'compound', + displayOnCreate: false, childMetadataFields: { - targetSampleActualSize: { - name: 'targetSampleActualSize', - displayName: 'Target Sample Size Actual', - title: 'Actual', - type: 'INT', - typeClass: 'primitive', - watermark: 'Enter an integer...', - description: 'Actual sample size.', + softwareName: { + name: 'softwareName', + displayName: 'Software Name', + title: 'Name', + type: 'TEXT', + watermark: '', + description: 'The name of software used to generate the Dataset', multiple: false, isControlledVocabulary: false, - displayFormat: '', + displayFormat: '#VALUE', isRequired: false, - displayOnCreate: true, - displayOrder: 8 + displayOrder: 70, + typeClass: 'primitive', + displayOnCreate: false }, - targetSampleSizeFormula: { - name: 'targetSampleSizeFormula', - displayName: 'Target Sample Size Formula', - title: 'Formula', + softwareVersion: { + name: 'softwareVersion', + displayName: 'Software Version', + title: 'Version', type: 'TEXT', - typeClass: 'primitive', watermark: '', - description: 'Formula used to determine target sample size.', + description: 'The version of the software used to generate the Dataset, e.g. 4.11', multiple: false, isControlledVocabulary: false, - displayFormat: '', + displayFormat: '#NAME: #VALUE', isRequired: false, - displayOnCreate: true, - displayOrder: 9 + displayOrder: 71, + typeClass: 'primitive', + displayOnCreate: false } } }, - deviationsFromSampleDesign: { - name: 'deviationsFromSampleDesign', - displayName: 'Major Deviations for Sample Design', - title: 'Major Deviations for Sample Design', - type: 'TEXT', - typeClass: 'primitive', + relatedMaterial: { + name: 'relatedMaterial', + displayName: 'Related Material', + title: 'Related Material', + type: 'TEXTBOX', watermark: '', description: - 'Show correspondence as well as discrepancies between the sampled units (obtained) and available statistics for the population (age, sex-ratio, marital status, etc.) as a whole.', - multiple: false, + 'Information, such as a persistent ID or citation, about the material related to the Dataset, such as appendices or sampling information available outside of the Dataset', + multiple: true, isControlledVocabulary: false, displayFormat: '', isRequired: false, - displayOnCreate: true, - displayOrder: 10 + displayOrder: 72, + typeClass: 'primitive', + displayOnCreate: false }, - collectionMode: { - name: 'collectionMode', - displayName: 'Collection Mode', - title: 'Collection Mode', + relatedDatasets: { + name: 'relatedDatasets', + displayName: 'Related Dataset', + title: 'Related Dataset', type: 'TEXTBOX', - typeClass: 'primitive', watermark: '', description: - 'Method used to collect the data; instrumentation characteristics (e.g., telephone interview, mail questionnaire, or other).', + "Information, such as a persistent ID or citation, about a related dataset, such as previous research on the Dataset's subject", multiple: true, isControlledVocabulary: false, displayFormat: '', isRequired: false, - displayOnCreate: true, - displayOrder: 11 + displayOrder: 73, + typeClass: 'primitive', + displayOnCreate: false }, - researchInstrument: { - name: 'researchInstrument', - displayName: 'Type of Research Instrument', - title: 'Type of Research Instrument', + otherReferences: { + name: 'otherReferences', + displayName: 'Other Reference', + title: 'Other Reference', type: 'TEXT', - typeClass: 'primitive', watermark: '', description: - 'Type of data collection instrument used. Structured indicates an instrument in which all respondents are asked the same questions/tests, possibly with precoded answers. If a small portion of such a questionnaire includes open-ended questions, provide appropriate comments. Semi-structured indicates that the research instrument contains mainly open-ended questions. Unstructured indicates that in-depth interviews were conducted.', - multiple: false, + 'Information, such as a persistent ID or citation, about another type of resource that provides background or supporting material to the Dataset', + multiple: true, isControlledVocabulary: false, displayFormat: '', isRequired: false, - displayOnCreate: true, - displayOrder: 12 + displayOrder: 74, + typeClass: 'primitive', + displayOnCreate: false }, - dataCollectionSituation: { - name: 'dataCollectionSituation', - displayName: 'Characteristics of Data Collection Situation', - title: 'Characteristics of Data Collection Situation', + dataSources: { + name: 'dataSources', + displayName: 'Data Source', + title: 'Data Source', type: 'TEXTBOX', - typeClass: 'primitive', watermark: '', description: - 'Description of noteworthy aspects of the data collection situation. Includes information on factors such as cooperativeness of respondents, duration of interviews, number of call backs, or similar.', - multiple: false, + 'Information, such as a persistent ID or citation, about sources of the Dataset (e.g. a book, article, serial, or machine-readable data file)', + multiple: true, isControlledVocabulary: false, displayFormat: '', isRequired: false, - displayOnCreate: true, - displayOrder: 13 - }, - actionsToMinimizeLoss: { - name: 'actionsToMinimizeLoss', - displayName: 'Actions to Minimize Losses', - title: 'Actions to Minimize Losses', - type: 'TEXT', + displayOrder: 75, typeClass: 'primitive', - watermark: '', - description: - 'Summary of actions taken to minimize data loss. Include information on actions such as follow-up visits, supervisory checks, historical matching, estimation, and so on.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 14 + displayOnCreate: false }, - controlOperations: { - name: 'controlOperations', - displayName: 'Control Operations', - title: 'Control Operations', - type: 'TEXT', - typeClass: 'primitive', + originOfSources: { + name: 'originOfSources', + displayName: 'Origin of Historical Sources', + title: 'Origin of Historical Sources', + type: 'TEXTBOX', watermark: '', description: - 'Control OperationsMethods to facilitate data control performed by the primary investigator or by the data archive.', + 'For historical sources, the origin and any rules followed in establishing them as sources', multiple: false, isControlledVocabulary: false, displayFormat: '', isRequired: false, - displayOnCreate: true, - displayOrder: 15 + displayOrder: 76, + typeClass: 'primitive', + displayOnCreate: false }, - weighting: { - name: 'weighting', - displayName: 'Weighting', - title: 'Weighting', + characteristicOfSources: { + name: 'characteristicOfSources', + displayName: 'Characteristic of Sources', + title: 'Characteristic of Sources', type: 'TEXTBOX', - typeClass: 'primitive', watermark: '', - description: - 'The use of sampling procedures might make it necessary to apply weights to produce accurate statistical results. Describes the criteria for using weights in analysis of a collection. If a weighting formula or coefficient was developed, the formula is provided, its elements are defined, and it is indicated how the formula was applied to the data.', + description: 'Characteristics not already noted elsewhere', multiple: false, isControlledVocabulary: false, displayFormat: '', isRequired: false, - displayOnCreate: true, - displayOrder: 16 - }, - cleaningOperations: { - name: 'cleaningOperations', - displayName: 'Cleaning Operations', - title: 'Cleaning Operations', - type: 'TEXT', + displayOrder: 77, typeClass: 'primitive', - watermark: '', - description: - 'Methods used to clean the data collection, such as consistency checking, wildcode checking, or other.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 17 + displayOnCreate: false }, - datasetLevelErrorNotes: { - name: 'datasetLevelErrorNotes', - displayName: 'Study Level Error Notes', - title: 'Study Level Error Notes', - type: 'TEXT', - typeClass: 'primitive', + accessToSources: { + name: 'accessToSources', + displayName: 'Documentation and Access to Sources', + title: 'Documentation and Access to Sources', + type: 'TEXTBOX', watermark: '', description: - 'Note element used for any information annotating or clarifying the methodology and processing of the study. ', + '1) Methods or procedures for accessing data sources and 2) any special permissions needed for access', multiple: false, isControlledVocabulary: false, displayFormat: '', isRequired: false, - displayOnCreate: true, - displayOrder: 18 - }, - responseRate: { - name: 'responseRate', - displayName: 'Response Rate', - title: 'Response Rate', - type: 'TEXTBOX', + displayOrder: 78, typeClass: 'primitive', - watermark: '', - description: 'Percentage of sample members who provided information.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 19 - }, - samplingErrorEstimates: { - name: 'samplingErrorEstimates', - displayName: 'Estimates of Sampling Error', - title: 'Estimates of Sampling Error', + displayOnCreate: false + } + } + }, + { + id: 2, + name: 'geospatial', + displayName: 'Geospatial Metadata', + displayOnCreate: false, + metadataFields: { + geographicUnit: { + name: 'geographicUnit', + displayName: 'Geographic Unit', + title: 'Geographic Unit', type: 'TEXT', - typeClass: 'primitive', watermark: '', description: - 'Measure of how precisely one can estimate a population value from a given sample.', + 'Lowest level of geographic aggregation covered by the Dataset, e.g., village, county, region.', multiple: false, isControlledVocabulary: false, displayFormat: '', isRequired: false, - displayOnCreate: true, - displayOrder: 20 - }, - otherDataAppraisal: { - name: 'otherDataAppraisal', - displayName: 'Other Forms of Data Appraisal', - title: 'Other Forms of Data Appraisal', - type: 'TEXT', + displayOrder: 5, typeClass: 'primitive', + displayOnCreate: true + }, + geographicCoverage: { + name: 'geographicCoverage', + displayName: 'Geographic Coverage', + title: 'Geographic Coverage', + type: 'NONE', watermark: '', description: - 'Other issues pertaining to the data appraisal. Describe issues such as response variance, nonresponse rate and testing for bias, interviewer and response bias, confidence levels, question bias, or similar.', + 'Information on the geographic coverage of the data. Includes the total geographic scope of the data.', multiple: false, isControlledVocabulary: false, displayFormat: '', - isRequired: false, + isRequired: true, + displayOrder: 0, + typeClass: 'compound', displayOnCreate: true, - displayOrder: 21 + childMetadataFields: { + country: { + name: 'country', + displayName: 'Geographic Coverage Country / Nation', + title: 'Country / Nation', + type: 'TEXT', + watermark: '', + description: 'The country or nation that the Dataset is about.', + multiple: false, + isControlledVocabulary: true, + controlledVocabularyValues: COUNTRY_FIELD_VOCAB_VALUES, + displayFormat: '#VALUE, ', + isRequired: true, + displayOrder: 1, + typeClass: 'controlledVocabulary', + displayOnCreate: false + }, + state: { + name: 'state', + displayName: 'Geographic Coverage State / Province', + title: 'State / Province', + type: 'TEXT', + watermark: '', + description: + 'The state or province that the Dataset is about. Use GeoNames for correct spelling and avoid abbreviations.', + multiple: false, + isControlledVocabulary: false, + displayFormat: '#VALUE, ', + isRequired: false, + displayOrder: 2, + typeClass: 'primitive', + displayOnCreate: false + }, + city: { + name: 'city', + displayName: 'Geographic Coverage City', + title: 'City', + type: 'TEXT', + watermark: '', + description: + 'The name of the city that the Dataset is about. Use GeoNames for correct spelling and avoid abbreviations.', + multiple: false, + isControlledVocabulary: false, + displayFormat: '#VALUE, ', + isRequired: false, + displayOrder: 3, + typeClass: 'primitive', + displayOnCreate: false + }, + otherGeographicCoverage: { + name: 'otherGeographicCoverage', + displayName: 'Geographic Coverage Other', + title: 'Other', + type: 'TEXT', + watermark: '', + description: 'Other information on the geographic coverage of the data.', + multiple: false, + isControlledVocabulary: false, + displayFormat: '#VALUE, ', + isRequired: false, + displayOrder: 4, + typeClass: 'primitive', + displayOnCreate: false + } + } }, - socialScienceNotes: { - name: 'socialScienceNotes', - displayName: 'Notes', - title: 'Notes', + geographicBoundingBox: { + name: 'geographicBoundingBox', + displayName: 'Geographic Bounding Box', + title: 'Geographic Bounding Box', type: 'NONE', - typeClass: 'compound', watermark: '', - description: 'General notes about this Dataset.', - multiple: false, + description: + "The fundamental geometric description for any Dataset that models geography is the geographic bounding box. It describes the minimum box, defined by west and east longitudes and north and south latitudes, which includes the largest geographic extent of the Dataset's geographic coverage. This element is used in the first pass of a coordinate-based search. Inclusion of this element in the codebook is recommended, but is required if the bound polygon box is included. ", + multiple: true, isControlledVocabulary: false, displayFormat: '', isRequired: false, - displayOnCreate: true, - displayOrder: 22, + displayOrder: 6, + typeClass: 'compound', + displayOnCreate: false, childMetadataFields: { - socialScienceNotesType: { - name: 'socialScienceNotesType', - displayName: 'Notes Type', - title: 'Type', + westLongitude: { + name: 'westLongitude', + displayName: 'Geographic Bounding Box Westernmost (Left) Longitude', + title: 'Westernmost (Left) Longitude', type: 'TEXT', - typeClass: 'primitive', watermark: '', - description: 'Type of note.', + description: + 'Westernmost coordinate delimiting the geographic extent of the Dataset. A valid range of values, expressed in decimal degrees, is -180.0 <= West Bounding Longitude Value <= 180.0.', multiple: false, isControlledVocabulary: false, displayFormat: '', isRequired: false, - displayOnCreate: true, - displayOrder: 23 + displayOrder: 7, + typeClass: 'primitive', + displayOnCreate: false }, - socialScienceNotesSubject: { - name: 'socialScienceNotesSubject', - displayName: 'Notes Subject', - title: 'Subject', + eastLongitude: { + name: 'eastLongitude', + displayName: 'Geographic Bounding Box Easternmost (Right) Longitude', + title: 'Easternmost (Right) Longitude', type: 'TEXT', - typeClass: 'primitive', watermark: '', - description: 'Note subject.', + description: + 'Easternmost coordinate delimiting the geographic extent of the Dataset. A valid range of values, expressed in decimal degrees, is -180.0 <= East Bounding Longitude Value <= 180.0.', multiple: false, isControlledVocabulary: false, displayFormat: '', isRequired: false, - displayOnCreate: true, - displayOrder: 24 + displayOrder: 8, + typeClass: 'primitive', + displayOnCreate: false }, - socialScienceNotesText: { - name: 'socialScienceNotesText', - displayName: 'Notes Text', - title: 'Text', - type: 'TEXTBOX', + northLatitude: { + name: 'northLatitude', + displayName: 'Geographic Bounding Box Northernmost (Top) Latitude', + title: 'Northernmost (Top) Latitude', + type: 'TEXT', + watermark: '', + description: + 'Northernmost coordinate delimiting the geographic extent of the Dataset. A valid range of values, expressed in decimal degrees, is -90.0 <= North Bounding Latitude Value <= 90.0.', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: false, + displayOrder: 9, typeClass: 'primitive', + displayOnCreate: false + }, + southLatitude: { + name: 'southLatitude', + displayName: 'Geographic Bounding Box Southernmost (Bottom) Latitude', + title: 'Southernmost (Bottom) Latitude', + type: 'TEXT', watermark: '', - description: 'Text for this note.', + description: + 'Southernmost coordinate delimiting the geographic extent of the Dataset. A valid range of values, expressed in decimal degrees, is -90.0 <= South Bounding Latitude Value <= 90.0.', multiple: false, isControlledVocabulary: false, displayFormat: '', isRequired: false, - displayOnCreate: true, - displayOrder: 25 + displayOrder: 10, + typeClass: 'primitive', + displayOnCreate: false } } } @@ -4867,3 +2307,256 @@ export class MetadataBlockInfoMother { ] } } + +export const SUBJECT_FIELD_VOCAB_VALUES = ['Subject1', 'Subject2', 'Subject3', 'Subject4'] +export const COUNTRY_FIELD_VOCAB_VALUES = [ + 'Afghanistan', + 'Albania', + 'Algeria', + 'American Samoa', + 'Andorra', + 'Angola', + 'Anguilla', + 'Antarctica', + 'Antigua and Barbuda', + 'Argentina', + 'Armenia', + 'Aruba', + 'Australia', + 'Austria', + 'Azerbaijan', + 'Bahamas', + 'Bahrain', + 'Bangladesh', + 'Barbados', + 'Belarus', + 'Belgium', + 'Belize', + 'Benin', + 'Bermuda', + 'Bhutan', + 'Bolivia, Plurinational State of', + 'Bonaire, Sint Eustatius and Saba', + 'Bosnia and Herzegovina', + 'Botswana', + 'Bouvet Island', + 'Brazil', + 'British Indian Ocean Territory', + 'Brunei Darussalam', + 'Bulgaria', + 'Burkina Faso', + 'Burundi', + 'Cambodia', + 'Cameroon', + 'Canada', + 'Cape Verde', + 'Cayman Islands', + 'Central African Republic', + 'Chad', + 'Chile', + 'China', + 'Christmas Island', + 'Cocos (Keeling) Islands', + 'Colombia', + 'Comoros', + 'Congo', + 'Congo, the Democratic Republic of the', + 'Cook Islands', + 'Costa Rica', + 'Croatia', + 'Cuba', + 'Curaçao', + 'Cyprus', + 'Czech Republic', + "Côte d'Ivoire", + 'Denmark', + 'Djibouti', + 'Dominica', + 'Dominican Republic', + 'Ecuador', + 'Egypt', + 'El Salvador', + 'Equatorial Guinea', + 'Eritrea', + 'Estonia', + 'Ethiopia', + 'Falkland Islands (Malvinas)', + 'Faroe Islands', + 'Fiji', + 'Finland', + 'France', + 'French Guiana', + 'French Polynesia', + 'French Southern Territories', + 'Gabon', + 'Gambia', + 'Georgia', + 'Germany', + 'Ghana', + 'Gibraltar', + 'Greece', + 'Greenland', + 'Grenada', + 'Guadeloupe', + 'Guam', + 'Guatemala', + 'Guernsey', + 'Guinea', + 'Guinea-Bissau', + 'Guyana', + 'Haiti', + 'Heard Island and Mcdonald Islands', + 'Holy See (Vatican City State)', + 'Honduras', + 'Hong Kong', + 'Hungary', + 'Iceland', + 'India', + 'Indonesia', + 'Iran, Islamic Republic of', + 'Iraq', + 'Ireland', + 'Isle of Man', + 'Israel', + 'Italy', + 'Jamaica', + 'Japan', + 'Jersey', + 'Jordan', + 'Kazakhstan', + 'Kenya', + 'Kiribati', + "Korea, Democratic People's Republic of", + 'Korea, Republic of', + 'Kuwait', + 'Kyrgyzstan', + "Lao People's Democratic Republic", + 'Latvia', + 'Lebanon', + 'Lesotho', + 'Liberia', + 'Libya', + 'Liechtenstein', + 'Lithuania', + 'Luxembourg', + 'Macao', + 'Macedonia, the Former Yugoslav Republic of', + 'Madagascar', + 'Malawi', + 'Malaysia', + 'Maldives', + 'Mali', + 'Malta', + 'Marshall Islands', + 'Martinique', + 'Mauritania', + 'Mauritius', + 'Mayotte', + 'Mexico', + 'Micronesia, Federated States of', + 'Moldova, Republic of', + 'Monaco', + 'Mongolia', + 'Montenegro', + 'Montserrat', + 'Morocco', + 'Mozambique', + 'Myanmar', + 'Namibia', + 'Nauru', + 'Nepal', + 'Netherlands', + 'New Caledonia', + 'New Zealand', + 'Nicaragua', + 'Niger', + 'Nigeria', + 'Niue', + 'Norfolk Island', + 'Northern Mariana Islands', + 'Norway', + 'Oman', + 'Pakistan', + 'Palau', + 'Palestine, State of', + 'Panama', + 'Papua New Guinea', + 'Paraguay', + 'Peru', + 'Philippines', + 'Pitcairn', + 'Poland', + 'Portugal', + 'Puerto Rico', + 'Qatar', + 'Romania', + 'Russian Federation', + 'Rwanda', + 'Réunion', + 'Saint Barthélemy', + 'Saint Helena, Ascension and Tristan da Cunha', + 'Saint Kitts and Nevis', + 'Saint Lucia', + 'Saint Martin (French part)', + 'Saint Pierre and Miquelon', + 'Saint Vincent and the Grenadines', + 'Samoa', + 'San Marino', + 'Sao Tome and Principe', + 'Saudi Arabia', + 'Senegal', + 'Serbia', + 'Seychelles', + 'Sierra Leone', + 'Singapore', + 'Sint Maarten (Dutch part)', + 'Slovakia', + 'Slovenia', + 'Solomon Islands', + 'Somalia', + 'South Africa', + 'South Georgia and the South Sandwich Islands', + 'South Sudan', + 'Spain', + 'Sri Lanka', + 'Sudan', + 'Suriname', + 'Svalbard and Jan Mayen', + 'Swaziland', + 'Sweden', + 'Switzerland', + 'Syrian Arab Republic', + 'Taiwan, Province of China', + 'Tajikistan', + 'Tanzania, United Republic of', + 'Thailand', + 'Timor-Leste', + 'Togo', + 'Tokelau', + 'Tonga', + 'Trinidad and Tobago', + 'Tunisia', + 'Turkey', + 'Turkmenistan', + 'Turks and Caicos Islands', + 'Tuvalu', + 'Uganda', + 'Ukraine', + 'United Arab Emirates', + 'United Kingdom', + 'United States', + 'United States Minor Outlying Islands', + 'Uruguay', + 'Uzbekistan', + 'Vanuatu', + 'Venezuela, Bolivarian Republic of', + 'Viet Nam', + 'Virgin Islands, British', + 'Virgin Islands, U.S.', + 'Wallis and Futuna', + 'Western Sahara', + 'Yemen', + 'Zambia', + 'Zimbabwe', + 'Åland Islands' +] diff --git a/tests/component/sections/create-dataset/CreateDataset.spec.tsx b/tests/component/sections/create-dataset/CreateDataset.spec.tsx index 3035e1cb4..e235de476 100644 --- a/tests/component/sections/create-dataset/CreateDataset.spec.tsx +++ b/tests/component/sections/create-dataset/CreateDataset.spec.tsx @@ -2,82 +2,38 @@ import { CreateDataset } from '../../../../src/sections/create-dataset/CreateDat import { DatasetRepository } from '../../../../src/dataset/domain/repositories/DatasetRepository' import { MetadataBlockInfoRepository } from '../../../../src/metadata-block-info/domain/repositories/MetadataBlockInfoRepository' import { MetadataBlockInfoMother } from '../../metadata-block-info/domain/models/MetadataBlockInfoMother' -import { TypeMetadataFieldOptions } from '../../../../src/metadata-block-info/domain/models/MetadataBlockInfo' import { NotImplementedModalProvider } from '../../../../src/sections/not-implemented/NotImplementedModalProvider' -import { UserMother } from '../../users/domain/models/UserMother' -import { UserRepository } from '../../../../src/users/domain/repositories/UserRepository' const datasetRepository: DatasetRepository = {} as DatasetRepository const metadataBlockInfoRepository: MetadataBlockInfoRepository = {} as MetadataBlockInfoRepository -const userRepository: UserRepository = {} as UserRepository const collectionMetadataBlocksInfo = MetadataBlockInfoMother.getByCollectionIdDisplayedOnCreateTrue() -const wrongCollectionMetadataBlocksInfo = - MetadataBlockInfoMother.wrongCollectionMetadataBlocksInfo() -const testUser = UserMother.create() - -const fillRequiredFields = () => { - cy.findByLabelText(/^Title/i).type('Test Dataset Title') - - cy.findByLabelText(/^Alternative Title/i).type('Alternative Title 1') - - cy.findByText('Author') - .closest('.row') - .within(() => { - cy.findByLabelText(/^Name/i).type('Test author name') - }) - - cy.findByText('Point of Contact') - .closest('.row') - .within(() => { - cy.findByLabelText(/^E-mail/i).type('test@test.com') - }) - - cy.findByText('Description') - .closest('.row') - .within(() => { - cy.findByLabelText(/^Text/i).type('Test description text') - }) - - cy.findByText('Subject') - .closest('.row') - .within(() => { - cy.findByLabelText('Toggle options menu').click() - - cy.findByText('Agricultural Sciences').should('exist') - cy.findByLabelText('Agricultural Sciences').click() - }) - - cy.findByText('Producer') - .closest('.row') - .within(() => { - cy.findByLabelText(/^Name/i).type('Test producer name') - }) - - // Compose field not multiple - cy.findByText('Composed field not multiple') - .closest('.row') - .within(() => { - cy.findByLabelText(/^Foo/i).type('Test foo name') - }) -} - describe('Create Dataset', () => { beforeEach(() => { datasetRepository.create = cy.stub().resolves({ persistentId: 'persistentId' }) metadataBlockInfoRepository.getDisplayedOnCreateByCollectionId = cy .stub() .resolves(collectionMetadataBlocksInfo) - userRepository.getAuthenticated = cy.stub().resolves(testUser) + }) + + it('renders the Host Collection Form for root collection', () => { + cy.customMount( + + ) + cy.findByText(/^Host Collection/i).should('exist') + cy.findByDisplayValue('root').should('exist') }) it('renders the Host Collection Form', () => { cy.customMount( @@ -92,494 +48,4 @@ describe('Create Dataset', () => { cy.findByText('Not Implemented').should('exist') }) }) - it('pre-fills the form with user data', () => { - cy.mountAuthenticated( - - - - ) - cy.findByText('Author') - .closest('.row') - .within(() => { - cy.findByLabelText(/^Name/i).should('have.value', testUser.displayName) - }) - cy.findByText('Author') - .closest('.row') - .within(() => { - cy.findByLabelText(/^Affiliation/i).should('have.value', testUser.affiliation) - }) - cy.findByText('Point of Contact') - .closest('.row') - .within(() => { - cy.findByLabelText(/^Name/i).should('have.value', testUser.displayName) - }) - cy.findByText('Point of Contact') - .closest('.row') - .within(() => { - cy.findByLabelText(/^Affiliation/i).should('have.value', testUser.affiliation) - }) - cy.findByLabelText(/^E-mail/i).should('have.value', testUser.email) - }) - it('renders the Create Dataset page and its metadata blocks sections', () => { - cy.customMount( - - ) - cy.findByText(/Create Dataset/i).should('exist') - cy.findByText(/Citation Metadata/i).should('exist') - cy.findByText(/Astronomy and Astrophysics Metadata/i).should('exist') - }) - - it('renders the Citation Metadata Form Fields correctly', () => { - cy.customMount( - - ) - // Check the first accordion item content - cy.get('.accordion > :nth-child(1)').within((_$accordionItem) => { - cy.findByText(/Citation Metadata/i).should('exist') - - // Title field - required - cy.findByText('Title').should('exist') - - cy.findByLabelText(/^Title/i) - .should('exist') - .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) - cy.findByText('Title').children('div').trigger('mouseover') - cy.document().its('body').findByText('The main title of the Dataset').should('exist') - - // Subtitle field - not required - cy.findByText('Subtitle').should('exist') - - cy.findByLabelText(/^Subtitle/i) - .should('exist') - .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) - - cy.findByLabelText(/^Subtitle/i).should('not.have.attr', 'required') - cy.findByText('Subtitle').children('div').trigger('mouseover') - cy.document() - .its('body') - .findByText( - 'A secondary title that amplifies or states certain limitations on the main title' - ) - .should('exist') - - // Author field - compound - cy.findByText('Author').should('exist') - - // Check properties inside the Author compound - cy.findByText('Author') - .closest('.row') - .within(() => { - // Author Name property - cy.findByLabelText(/Name/).should('exist') - // Author identifier - Vocabulary - cy.findByLabelText(/Identifier Type/).should('exist') - cy.findByLabelText(/Identifier Type/).should('have.prop', 'tagName', 'SELECT') - cy.findByLabelText(/Identifier Type/) - .children('option') - .should('have.length', 9) - }) - - // Notes field - TEXTBOX - cy.findByText('Notes').should('exist') - cy.findByLabelText(/Notes/) - .should('exist') - .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Textbox) - cy.findByLabelText(/Notes/).should('have.prop', 'tagName', 'TEXTAREA') - - // Alternative URL field - URL - cy.findByText('Alternative URL').should('exist') - cy.findByLabelText(/Alternative URL/) - .should('exist') - .should('have.data', 'fieldtype', TypeMetadataFieldOptions.URL) - - // E-mail field - EMAIL - cy.findByText('E-mail').should('exist') - cy.findByLabelText(/E-mail/) - .should('exist') - .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Email) - - // Description.Date field - DATE - cy.findByText('Date').should('exist') - cy.findByLabelText(/Date/) - .should('exist') - .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Date) - }) - - // Subject field - VOCABULARY and MULTIPLE - cy.findByText('Subject').should('exist') - cy.findByLabelText(/Subject/).should('exist') - - cy.findByText(/Save Dataset/i).should('exist') - - cy.findByText(/Cancel/i).should('exist') - }) - - it('renders the Astronomy and Astrophysics Metadata Form Fields correctly', () => { - cy.customMount( - - ) - - cy.get('.accordion > :nth-child(2)').within((_$accordionItem) => { - cy.findByText(/Astronomy and Astrophysics Metadata/i).should('exist') - - // Depth Coverage field - FLOAT - cy.findByText('Depth Coverage').should('exist') - cy.findByLabelText(/Depth Coverage/) - .should('exist') - .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Float) - - // Integer Something field - INT - cy.findByText('Object Count').should('exist') - cy.findByLabelText(/Object Count/) - .should('exist') - .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Int) - }) - }) - - it('should display required errors when submitting the form with required fields empty', () => { - cy.customMount( - - ) - - cy.findByText(/Save Dataset/i).click() - - cy.findByText('Title is required').should('exist') - cy.findByLabelText(/^Title/i).should('have.focus') - - cy.findByText('Author Name is required').should('exist') - cy.findByText('Point of Contact E-mail is required').should('exist') - cy.findByText('Description Text is required').should('exist') - cy.findByText('Subject is required').should('exist') - cy.findByText('Producer Name is required').should('exist') - }) - - it('should not display required errors when submitting the form with required fields filled', () => { - cy.customMount( - - ) - - fillRequiredFields() - - cy.findByText(/Save Dataset/i).click() - - cy.findByText('Author Name is required').should('not.exist') - cy.findByText('Point of Contact E-mail is required').should('not.exist') - cy.findByText('Description Text is required').should('not.exist') - cy.findByText('Subject is required').should('not.exist') - cy.findByText('Producer Name is required').should('not.exist') - }) - - it('should show correct errors when filling inputs with invalid formats', () => { - cy.customMount( - - ) - - cy.findByLabelText(/Alternative URL/).type('html://test.com') - cy.findByText('Alternative URL is not a valid URL').should('exist') - - cy.findByText('Description') - .closest('.row') - .within(() => { - cy.findByLabelText(/Date/).type('1990-23-23') - cy.findByText(/^Description Date is not a valid date./).should('exist') - }) - - cy.findByText('Point of Contact') - .closest('.row') - .within(() => { - cy.findByLabelText(/^E-mail/i).type('test') - cy.findByText('Point of Contact E-mail is not a valid email').should('exist') - }) - - // We need to open the Astronomy and Astrophysics Metadata accordion to fill the fields first, in this metadatablock we have ints and floats fields - cy.get(':nth-child(2) > .accordion-header > .accordion-button').click() - - cy.findByLabelText(/Depth Coverage/).type('30L') - cy.findByText('Depth Coverage is not a valid float').should('exist') - - // Object Count - cy.findByLabelText(/Object Count/).type('30.5') - cy.findByText('Object Count is not a valid integer').should('exist') - }) - - it('should not show errors when filling inputs with valid formats', () => { - cy.customMount( - - ) - - cy.findByLabelText(/Alternative URL/).type('https://test.com') - cy.findByText('Alternative URL is not a valid URL').should('not.exist') - - cy.findByText('Description') - .closest('.row') - .within(() => { - cy.findByLabelText(/Date/).type('1990-01-23') - cy.findByText(/^Description Date is not a valid date./).should('not.exist') - }) - - cy.findByText('Point of Contact') - .closest('.row') - .within(() => { - cy.findByLabelText(/^E-mail/i).type('test@test.com') - cy.findByText('Point of Contact E-mail is not a valid email').should('not.exist') - }) - - // We need to open the Astronomy and Astrophysics Metadata accordion to fill the fields first, in this metadatablock we have ints and floats fields - cy.get(':nth-child(2) > .accordion-header > .accordion-button').click() - - cy.findByLabelText(/Depth Coverage/).type('3.14159265') - cy.findByText('Depth Coverage is not a valid float').should('not.exist') - - // Object Count - cy.findByLabelText(/Object Count/).type('30') - cy.findByText('Object Count is not a valid integer').should('not.exist') - }) - - it('renders skeleton while loading', () => { - cy.customMount( - - ) - - cy.findByTestId('dataset-form-skeleton').should('exist') - }) - - describe('When getting collection metadata blocks info fails', () => { - it('renders error message', () => { - metadataBlockInfoRepository.getDisplayedOnCreateByCollectionId = cy - .stub() - .rejects(new Error('some error')) - - cy.customMount( - - ) - - cy.findByText('Error').should('exist') - }) - - it('disables save button', () => { - metadataBlockInfoRepository.getDisplayedOnCreateByCollectionId = cy - .stub() - .rejects(new Error('some error')) - - cy.customMount( - - ) - - cy.findByText(/Save Dataset/i).should('be.disabled') - }) - }) - - describe('When dataset creation fails', () => { - it('should show create error message from the client-javascript client when the dataset creation fails', () => { - datasetRepository.create = cy - .stub() - .rejects(new Error('Error from the api javascript client')) - metadataBlockInfoRepository.getDisplayedOnCreateByCollectionId = cy - .stub() - .resolves(wrongCollectionMetadataBlocksInfo) - - cy.customMount( - - ) - - cy.findByText(/Save Dataset/i).click() - - cy.findByText('Validation Error').should('exist') - cy.findByText(/Error from the api javascript client/).should('exist') - }) - - it('should only show field error part of the error message from the api when the dataset creation fails', () => { - datasetRepository.create = cy - .stub() - .rejects( - new Error( - 'Validation Failed: Point of Contact E-mail test@test.c is not a valid email address. (Invalid value:edu.harvard.iq.dataverse.DatasetFieldValueValue[ id=null ]).java.util.stream.ReferencePipeline$3@561b5200' - ) - ) - - cy.customMount( - - ) - - fillRequiredFields() - - cy.findByText(/Save Dataset/i).click() - - cy.findByText('Validation Error').should('exist') - cy.findByText(/Point of Contact E-mail test@test.c is not a valid email address./).should( - 'exist' - ) - }) - - it('should show locale error message when the dataset creation fails with an unknown error message', () => { - datasetRepository.create = cy.stub().rejects('Some not expected error') - - cy.customMount( - - ) - - fillRequiredFields() - - cy.findByText(/Save Dataset/i).click() - - cy.findByText('Validation Error').should('exist') - cy.findByText( - /Required fields were missed or there was a validation error. Please scroll down to see details./ - ).should('exist') - }) - }) - - it('can submit a valid form', () => { - cy.customMount( - - ) - - fillRequiredFields() - - cy.findByText(/Save Dataset/i).click() - cy.findByText('Form submitted successfully!').should('exist') - }) - - it('shows an error message when the submission fails', () => { - datasetRepository.create = cy.stub().rejects(new Error('some error')) - cy.customMount( - - ) - - fillRequiredFields() - - cy.findByText(/Save Dataset/i).click() - cy.contains('Validation Error').should('exist') - }) - - it('cancel button is clickable', () => { - cy.customMount( - - ) - - cy.findByText(/Cancel/i).click() - }) - - it('open closed accordion that has fields with errors on it and scrolls to the focused field', () => { - cy.customMount( - - ) - - cy.get(':nth-child(1) > .accordion-header > .accordion-button').click() - - cy.findByText('Save Dataset').click() - - cy.findByText('Title is required').should('exist') - cy.findByLabelText(/^Title/i) - .should('have.focus') - .should('be.visible') - - cy.findByLabelText(/^Title/i).type('Test Dataset Title') - - cy.get(':nth-child(1) > .accordion-header > .accordion-button').click() - - cy.findByText('Save Dataset').click() - - cy.findByText('Alternative Title is required').should('exist') - cy.findByLabelText(/^Alternative Title/i) - .should('have.focus') - .should('be.visible') - }) - - it('adds a new field and removes a field to composed field multiple', () => { - cy.customMount( - - ) - - cy.findByText('Author') - .closest('.row') - .within(() => { - cy.findByLabelText(/^Name/i).type('Test foo name') - - cy.findByLabelText(`Add Author`).click() - - cy.findByLabelText(`Delete Author`).should('exist') - - cy.findByLabelText(`Delete Author`).click() - - cy.findByLabelText(`Delete Author`).should('not.exist') - }) - }) - - it('adds a new field and removes a field to a primitive multiple field', () => { - cy.customMount( - - ) - - cy.findByLabelText(/^Alternative Title/i).type('Alternative Title 1') - - cy.findByLabelText(`Add Alternative Title`).click() - - cy.findByLabelText(`Delete Alternative Title`).should('exist') - - cy.findByLabelText(`Delete Alternative Title`).click() - - cy.findByLabelText(`Delete Alternative Title`).should('not.exist') - }) }) diff --git a/tests/component/sections/create-dataset/MetadataFieldsHelper.spec.ts b/tests/component/sections/create-dataset/MetadataFieldsHelper.spec.ts deleted file mode 100644 index c8da1937b..000000000 --- a/tests/component/sections/create-dataset/MetadataFieldsHelper.spec.ts +++ /dev/null @@ -1,332 +0,0 @@ -import { DatasetDTO } from '../../../../src/dataset/domain/useCases/DTOs/DatasetDTO' -import { MetadataBlockInfo } from '../../../../src/metadata-block-info/domain/models/MetadataBlockInfo' -import { - CreateDatasetFormValues, - MetadataFieldsHelper -} from '../../../../src/sections/create-dataset/MetadataFieldsHelper' - -const sampleObjectWithSlashKeys: CreateDatasetFormValues = { - blockOne: { - 'key/one': 'value1', - 'key/two': { - 'key/childOne': 'value3', - 'key/childTwo': 'value4' - }, - key5: { - key6: 'value6' - }, - key6: [ - { - 'key/childOne': 'value3', - 'key/childTwo': 'value4' - } - ] - }, - blockTwo: { - 'key/three': 'value7', - 'key/four': 'value8' - } -} - -const replacedSampleObjectWithSlashKeys: CreateDatasetFormValues = { - blockOne: { - 'key.one': 'value1', - 'key.two': { - 'key.childOne': 'value3', - 'key.childTwo': 'value4' - }, - key5: { - key6: 'value6' - }, - key6: [ - { - 'key.childOne': 'value3', - 'key.childTwo': 'value4' - } - ] - }, - blockTwo: { - 'key.three': 'value7', - 'key.four': 'value8' - } -} - -const sampleArrayOfMetadataBlocksInfo: MetadataBlockInfo[] = [ - { - id: 1, - name: 'citation', - displayName: 'Citation Metadata', - displayOnCreate: true, - metadataFields: { - title: { - name: 'title', - displayName: 'Title', - title: 'Title', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: 'The main title of the Dataset', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: true, - displayOnCreate: true, - displayOrder: 0 - } - } - }, - { - id: 4, - name: 'astrophysics', - displayName: 'Astronomy and Astrophysics Metadata', - displayOnCreate: true, - metadataFields: { - 'coverage.Temporal': { - name: 'coverage.Temporal', - displayName: 'Dataset Date Range', - title: 'Dataset Date Range', - type: 'NONE', - typeClass: 'compound', - watermark: '', - description: ' Time period covered by the data.', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 12, - childMetadataFields: { - 'coverage.Temporal.StartTime': { - name: 'coverage.Temporal.StartTime', - displayName: 'Dataset Date Range Start', - title: 'Start', - type: 'DATE', - typeClass: 'primitive', - watermark: 'YYYY-MM-DD', - description: 'Dataset Start Date', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 13 - }, - 'coverage.Temporal.StopTime': { - name: 'coverage.Temporal.StopTime', - displayName: 'Dataset Date Range End', - title: 'End', - type: 'DATE', - typeClass: 'primitive', - watermark: 'YYYY-MM-DD', - description: 'Dataset End Date', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 14 - } - } - }, - 'resolution.Spatial': { - name: 'resolution.Spatial', - displayName: 'Spatial Resolution', - title: 'Spatial Resolution', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: - 'The spatial (angular) resolution that is typical of the observations, in decimal degrees.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 4 - } - } - } -] - -const sampleArrayOfMetadataBlocksInfoWithSlashNames: MetadataBlockInfo[] = [ - { - id: 1, - name: 'citation', - displayName: 'Citation Metadata', - displayOnCreate: true, - metadataFields: { - title: { - name: 'title', - displayName: 'Title', - title: 'Title', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: 'The main title of the Dataset', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: true, - displayOnCreate: true, - displayOrder: 0 - } - } - }, - { - id: 4, - name: 'astrophysics', - displayName: 'Astronomy and Astrophysics Metadata', - displayOnCreate: true, - metadataFields: { - 'coverage.Temporal': { - name: 'coverage/Temporal', - displayName: 'Dataset Date Range', - title: 'Dataset Date Range', - type: 'NONE', - typeClass: 'compound', - watermark: '', - description: ' Time period covered by the data.', - multiple: true, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 12, - childMetadataFields: { - 'coverage.Temporal.StartTime': { - name: 'coverage/Temporal/StartTime', - displayName: 'Dataset Date Range Start', - title: 'Start', - type: 'DATE', - typeClass: 'primitive', - watermark: 'YYYY-MM-DD', - description: 'Dataset Start Date', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 13 - }, - 'coverage.Temporal.StopTime': { - name: 'coverage/Temporal/StopTime', - displayName: 'Dataset Date Range End', - title: 'End', - type: 'DATE', - typeClass: 'primitive', - watermark: 'YYYY-MM-DD', - description: 'Dataset End Date', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 14 - } - } - }, - 'resolution.Spatial': { - name: 'resolution/Spatial', - displayName: 'Spatial Resolution', - title: 'Spatial Resolution', - type: 'TEXT', - typeClass: 'primitive', - watermark: '', - description: - 'The spatial (angular) resolution that is typical of the observations, in decimal degrees.', - multiple: false, - isControlledVocabulary: false, - displayFormat: '', - isRequired: false, - displayOnCreate: true, - displayOrder: 4 - } - } - } -] - -const formValues: CreateDatasetFormValues = { - citation: { - title: 'Dataset Title', - subtitle: '', - aPrimitiveMultiple: [{ value: 'something' }, { value: '' }], - anEmptyPrimitiveMultiple: [{ value: '' }], - author: [ - { - authorName: 'Author Name', - authorAffiliation: 'Author Affiliation', - someEmptyField: '' - }, - { - authorName: '', - authorAffiliation: '', - someEmptyField: '' - } - ], - subject: ['Subject 1', 'Subject 2'], - anEmptyVocabulary: [] - }, - astrophysics: { - 'coverage.Temporal': [ - { - 'coverage.Temporal.StartTime': '2022-01-01', - 'coverage.Temporal.StopTime': '2022-12-31' - } - ], - 'resolution.Spatial': '100' - } -} - -const expectedDatasetDTO: DatasetDTO = { - metadataBlocks: [ - { - name: 'citation', - fields: { - title: 'Dataset Title', - aPrimitiveMultiple: ['something'], - author: [ - { - authorName: 'Author Name', - authorAffiliation: 'Author Affiliation' - } - ], - subject: ['Subject 1', 'Subject 2'] - } - }, - { - name: 'astrophysics', - fields: { - 'coverage.Temporal': [ - { - 'coverage.Temporal.StartTime': '2022-01-01', - 'coverage.Temporal.StopTime': '2022-12-31' - } - ], - 'resolution.Spatial': '100' - } - } - ] -} - -describe('Test MetadataFieldsHelper', () => { - it('should replace dot keys with slash', () => { - const replacedToSlashObject = MetadataFieldsHelper.replaceDotNamesKeysWithSlash( - sampleArrayOfMetadataBlocksInfo - ) - - expect(replacedToSlashObject).to.deep.equal(sampleArrayOfMetadataBlocksInfoWithSlashNames) - }) - - it('should replace slash keys with dot', () => { - const replacedToKeysObject = - MetadataFieldsHelper.replaceSlashKeysWithDot(sampleObjectWithSlashKeys) - - expect(replacedToKeysObject).to.deep.equal(replacedSampleObjectWithSlashKeys) - }) - - it('should format form values to create dataset DTO', () => { - const datasetDTO = MetadataFieldsHelper.formatFormValuesToCreateDatasetDTO(formValues) - - expect(datasetDTO).to.deep.equal(expectedDatasetDTO) - }) -}) diff --git a/tests/component/sections/dataset/Dataset.spec.tsx b/tests/component/sections/dataset/Dataset.spec.tsx index ffed83853..846944cb9 100644 --- a/tests/component/sections/dataset/Dataset.spec.tsx +++ b/tests/component/sections/dataset/Dataset.spec.tsx @@ -13,6 +13,7 @@ import { FilesWithCount } from '../../../../src/files/domain/models/FilesWithCou import { FilesCountInfoMother } from '../../files/domain/models/FilesCountInfoMother' import { FileType } from '../../../../src/files/domain/models/FileMetadata' import { FileAccessOption, FileTag } from '../../../../src/files/domain/models/FileCriteria' +import { AlertProvider } from '../../../../src/sections/alerts/AlertProvider' const setAnonymizedView = () => {} const fileRepository: FileRepository = {} as FileRepository @@ -159,4 +160,32 @@ describe('Dataset', () => { cy.findByRole('columnheader', { name: /Files/ }).should('exist') cy.findByText('10 of 200 Files displayed').should('exist') }) + + it('renders the dataset created alert correctly', () => { + const testDataset = DatasetMother.create() + mountWithDataset( + + + , + testDataset + ) + + cy.findAllByText(testDataset.version.title).should('exist') + + cy.findByText(/This dataset has been created./).should('exist') + }) + + it('renders the dataset updated metadata alert correctly', () => { + const testDataset = DatasetMother.create() + mountWithDataset( + + + , + testDataset + ) + + cy.findAllByText(testDataset.version.title).should('exist') + + cy.findByText(/The metadata for this dataset has been updated./).should('exist') + }) }) diff --git a/tests/component/sections/dataset/dataset-files/DatasetFilesScrollable.spec.tsx b/tests/component/sections/dataset/dataset-files/DatasetFilesScrollable.spec.tsx index 0ce8ab03b..af234d27a 100644 --- a/tests/component/sections/dataset/dataset-files/DatasetFilesScrollable.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/DatasetFilesScrollable.spec.tsx @@ -65,8 +65,6 @@ const testFilesCountInfo = FilesCountInfoMother.create({ }) const settingsRepository = {} as SettingRepository -// TODO:ME Test everything here, to difficult to test passing ref of sentry - describe('DatasetFilesScrollable', () => { beforeEach(() => { fileRepository.getAllByDatasetPersistentIdWithCount = cy.stub().resolves(testFiles) @@ -363,9 +361,11 @@ describe('DatasetFilesScrollable', () => { /> ) cy.findByRole('columnheader', { name: '10 of 200 Files displayed' }).should('exist') - cy.get('table > thead > tr > th > input[type=checkbox]').click() + cy.get('table > thead > tr > th > input[type=checkbox]').click({ force: true }) cy.findByText('10 files are currently selected.').should('exist') - cy.findByRole('button', { name: 'Select all 200 files in this dataset.' }).click() + cy.findByRole('button', { name: 'Select all 200 files in this dataset.' }).click({ + force: true + }) cy.findByText('200 files are currently selected.').should('exist') }) diff --git a/tests/component/sections/edit-dataset-metadata/EditDatasetMetadata.spec.tsx b/tests/component/sections/edit-dataset-metadata/EditDatasetMetadata.spec.tsx new file mode 100644 index 000000000..fab09862a --- /dev/null +++ b/tests/component/sections/edit-dataset-metadata/EditDatasetMetadata.spec.tsx @@ -0,0 +1,93 @@ +import { ReactNode } from 'react' +import { DatasetRepository } from '../../../../src/dataset/domain/repositories/DatasetRepository' +import { DatasetMother } from '../../dataset/domain/models/DatasetMother' +import { LoadingProvider } from '../../../../src/sections/loading/LoadingProvider' +import { Dataset as DatasetModel } from '../../../../src/dataset/domain/models/Dataset' +import { DatasetProvider } from '../../../../src/sections/dataset/DatasetProvider' +import { MetadataBlockInfoMother } from '../../metadata-block-info/domain/models/MetadataBlockInfoMother' +import { EditDatasetMetadata } from '../../../../src/sections/edit-dataset-metadata/EditDatasetMetadata' +import { MetadataBlockInfoRepository } from '../../../../src/metadata-block-info/domain/repositories/MetadataBlockInfoRepository' + +const datasetRepository: DatasetRepository = {} as DatasetRepository +const metadataBlockInfoRepository: MetadataBlockInfoRepository = {} as MetadataBlockInfoRepository + +const dataset = DatasetMother.createRealistic() +const metadataBlocksInfoOnEditMode = + MetadataBlockInfoMother.getByCollectionIdDisplayedOnCreateFalse() + +describe('EditDatasetMetadata', () => { + const mountWithDataset = (component: ReactNode, dataset: DatasetModel | undefined) => { + const searchParams = { persistentId: 'some-persistent-id' } + + datasetRepository.getByPersistentId = cy.stub().resolves(dataset) + metadataBlockInfoRepository.getByColecctionId = cy.stub().resolves(metadataBlocksInfoOnEditMode) + datasetRepository.updateMetadata = cy.stub().resolves(undefined) + + cy.customMount( + + + {component} + + + ) + } + + it('renders the correct breadcrumbs', () => { + mountWithDataset( + , + dataset + ) + + cy.findByRole('link', { name: 'Root' }).should('exist') + + cy.findByRole('link', { name: 'Root' }) + .closest('.breadcrumb') + .within(() => { + cy.findByRole('link', { name: 'Dataset Title' }).should('exist') + cy.findByText('Edit Dataset Metadata').should('exist') + }) + }) + + it('renders skeleton while loading', () => { + mountWithDataset( + , + dataset + ) + + cy.findByTestId('edit-dataset-metadata-skeleton').should('exist') + }) + + it('renders the not found page when dataset is not founded', () => { + const emptyDataset = DatasetMother.createEmpty() + + mountWithDataset( + , + emptyDataset + ) + + cy.findByText('Page Not Found').should('exist') + }) + + it('renders the Host Collection', () => { + mountWithDataset( + , + dataset + ) + + cy.findByTestId('edit-dataset-metadata-skeleton').should('not.exist') + cy.findByText(/^Host Collection/i).should('exist') + cy.findByDisplayValue('Root').should('exist').should('have.attr', 'readonly', 'readonly') + }) +}) diff --git a/tests/component/sections/shared/add-data-actions/AddDataActionsButton.spec.tsx b/tests/component/sections/shared/add-data-actions/AddDataActionsButton.spec.tsx index 0bb7a98d8..679e72848 100644 --- a/tests/component/sections/shared/add-data-actions/AddDataActionsButton.spec.tsx +++ b/tests/component/sections/shared/add-data-actions/AddDataActionsButton.spec.tsx @@ -5,5 +5,27 @@ describe('AddDataActionsButton', () => { cy.customMount() cy.findByRole('button', { name: /Add Data/i }).should('exist') + cy.findByRole('button', { name: /Add Data/ }).click() + cy.findByText('New Collection').should('be.visible') + cy.findByText('New Dataset').should('be.visible') + }) + + it('renders the new dataset button with the correct generated link', () => { + cy.customMount() + + cy.findByRole('button', { name: /Add Data/i }).click() + cy.findByText('New Dataset') + .should('be.visible') + .should('have.attr', 'href', '/datasets/create') + }) + + it('renders the new dataset button with the correct generated link', () => { + const collectionId = 'some-collection-id' + cy.customMount() + + cy.findByRole('button', { name: /Add Data/i }).click() + cy.findByText('New Dataset') + .should('be.visible') + .should('have.attr', 'href', `/datasets/create?collectionId=${collectionId}`) }) }) diff --git a/tests/component/sections/shared/dataset-metadata-form/DatasetMetadataForm.spec.tsx b/tests/component/sections/shared/dataset-metadata-form/DatasetMetadataForm.spec.tsx new file mode 100644 index 000000000..728d708f9 --- /dev/null +++ b/tests/component/sections/shared/dataset-metadata-form/DatasetMetadataForm.spec.tsx @@ -0,0 +1,1897 @@ +import { MetadataBlockName } from '../../../../../src/dataset/domain/models/Dataset' +import { DatasetRepository } from '../../../../../src/dataset/domain/repositories/DatasetRepository' +import { TypeMetadataFieldOptions } from '../../../../../src/metadata-block-info/domain/models/MetadataBlockInfo' +import { MetadataBlockInfoRepository } from '../../../../../src/metadata-block-info/domain/repositories/MetadataBlockInfoRepository' +import { DatasetMetadataForm } from '../../../../../src/sections/shared/form/DatasetMetadataForm' +import { UserRepository } from '../../../../../src/users/domain/repositories/UserRepository' +import { DatasetMother } from '../../../dataset/domain/models/DatasetMother' +import { MetadataBlockInfoMother } from '../../../metadata-block-info/domain/models/MetadataBlockInfoMother' +import { UserMother } from '../../../users/domain/models/UserMother' + +const datasetRepository: DatasetRepository = {} as DatasetRepository +const metadataBlockInfoRepository: MetadataBlockInfoRepository = {} as MetadataBlockInfoRepository +const userRepository: UserRepository = {} as UserRepository + +const dataset = DatasetMother.createRealistic() +const datasetWithAstroBlock = DatasetMother.createRealistic({ + metadataBlocks: [ + ...dataset.metadataBlocks, + { + name: MetadataBlockName.ASTROPHYSICS, + fields: { + 'coverage.ObjectDensity': '23.35', + 'coverage.ObjectCount': '50' + } + } + ] +}) + +const metadataBlocksInfoOnCreateMode = + MetadataBlockInfoMother.getByCollectionIdDisplayedOnCreateTrue() + +const metadataBlocksInfoOnCreateModeWithAstroBlock = + MetadataBlockInfoMother.getByCollectionIdDisplayedOnCreateTrue({ + id: 4, + name: 'astrophysics', + displayName: 'Astronomy and Astrophysics Metadata', + displayOnCreate: false, + metadataFields: { + 'coverage.ObjectDensity': { + name: 'coverage.ObjectDensity', + displayName: 'Object Density', + title: 'Object Density', + type: 'FLOAT', + watermark: 'Enter a floating-point number.', + description: + 'The (typical) density of objects, catalog entries, telescope pointings, etc., on the sky, in number per square degree.', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOrder: 17, + typeClass: 'primitive', + displayOnCreate: false + }, + 'coverage.ObjectCount': { + name: 'coverage.ObjectCount', + displayName: 'Object Count', + title: 'Object Count', + type: 'INT', + watermark: 'Enter an integer.', + description: 'The total number of objects, catalog entries, etc., in the data object.', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOrder: 18, + typeClass: 'primitive', + displayOnCreate: false + }, + someDate: { + name: 'someDate', + displayName: 'Some Date in either of the formats', + title: 'Some Date', + type: 'DATE', + typeClass: 'primitive', + watermark: 'YYYY or YYYY-MM or YYYY-MM-DD', + description: 'Some description', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: false, + displayOnCreate: true, + displayOrder: 3 + } + } + }) + +const metadataBlocksInfoOnCreateModeWithComposedNotMultipleField = + MetadataBlockInfoMother.getByCollectionIdDisplayedOnCreateTrue({ + id: 4, + name: 'astrophysics', + displayName: 'Astronomy and Astrophysics Metadata', + displayOnCreate: false, + metadataFields: { + producer: { + name: 'producer', + displayName: 'Producer', + title: 'Producer', + type: 'NONE', + watermark: '', + description: + 'The entity, such a person or organization, managing the finances or other administrative processes involved in the creation of the Dataset', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: false, + displayOrder: 36, + typeClass: 'compound', + displayOnCreate: false, + childMetadataFields: { + producerName: { + name: 'producerName', + displayName: 'Producer Name', + title: 'Name', + type: 'TEXT', + watermark: '1) FamilyName, GivenName or 2) Organization', + description: + "The name of the entity, e.g. the person's name or the name of an organization", + multiple: false, + isControlledVocabulary: false, + displayFormat: '#VALUE', + isRequired: true, + displayOrder: 37, + typeClass: 'primitive', + displayOnCreate: false + }, + producerAffiliation: { + name: 'producerAffiliation', + displayName: 'Producer Affiliation', + title: 'Affiliation', + type: 'TEXT', + watermark: 'Organization XYZ', + description: + "The name of the entity affiliated with the producer, e.g. an organization's name", + multiple: false, + isControlledVocabulary: false, + displayFormat: '(#VALUE)', + isRequired: false, + displayOrder: 38, + typeClass: 'primitive', + displayOnCreate: false + } + } + } + } + }) + +const metadataBlocksInfoOnEditMode = + MetadataBlockInfoMother.getByCollectionIdDisplayedOnCreateFalse() +const wrongCollectionMetadataBlocksInfo = + MetadataBlockInfoMother.wrongCollectionMetadataBlocksInfo() +const testUser = UserMother.create() + +const fillRequiredFieldsOnCreate = () => { + cy.findByLabelText(/^Title/i).type('Test Dataset Title') + + cy.findByText('Author') + .closest('.row') + .within(() => { + cy.findByLabelText(/^Name/i).type('Test author name') + }) + + cy.findByText('Point of Contact') + .closest('.row') + .within(() => { + cy.findByLabelText(/^E-mail/i).type('test@test.com') + }) + + cy.findByText('Description') + .closest('.row') + .within(() => { + cy.findByLabelText(/^Text/i).type('Test description text') + }) + + cy.findByText('Subject') + .closest('.row') + .within(() => { + cy.findByLabelText('Toggle options menu').click() + + cy.findByText('Subject1').should('exist') + cy.findByLabelText('Subject1').click() + }) + + cy.findByText('Geographic Coverage') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText(/Country \/ Nation/) + .should('exist') + .select('Argentina', { force: true }) + }) +} + +describe('DatasetMetadataForm', () => { + beforeEach(() => { + datasetRepository.getByPersistentId = cy.stub().resolves(dataset) + datasetRepository.create = cy.stub().resolves({ persistentId: 'persistentId' }) + datasetRepository.updateMetadata = cy.stub().resolves(undefined) + metadataBlockInfoRepository.getByColecctionId = cy.stub().resolves(metadataBlocksInfoOnEditMode) + metadataBlockInfoRepository.getDisplayedOnCreateByCollectionId = cy + .stub() + .resolves(metadataBlocksInfoOnCreateMode) + + userRepository.getAuthenticated = cy.stub().resolves(testUser) + }) + + it('renders the form in create mode', () => { + cy.customMount( + + ) + + cy.findByTestId('metadata-form').should('exist') + }) + + it('renders the form in edit mode', () => { + cy.customMount( + + ) + + cy.findByTestId('metadata-form').should('exist') + }) + + it('renders the correct metadata block form sections', () => { + cy.customMount( + + ) + cy.findByText(/Citation Metadata/i).should('exist') + cy.findByText(/Geospatial Metadata/i).should('exist') + cy.findByText(/Social Sciences and Humanities Metadata/i).should('not.exist') + cy.findByText(/Astronomy and Astrophysics Metadata/i).should('not.exist') + cy.findByText(/Life Sciences Metadata/i).should('not.exist') + cy.findByText(/Journal Metadata/i).should('not.exist') + }) + + describe('renders the Citation and Geospatial Metadata Form Fields correctly on create mode', () => { + beforeEach(() => { + cy.customMount( + + ) + }) + it('renders the correct form fields from Citation Metadata Block', () => { + // 1st Accordion with Citation Metadata + cy.get('.accordion > :nth-child(1)').within(() => { + cy.findByText(/Citation Metadata/i).should('exist') + + cy.findByText('Title').should('exist') + cy.findByLabelText(/^Title/i) + .should('exist') + .should('have.attr', 'aria-required', 'true') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + // Composed field + cy.findByText('Author') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText(/Name/) + .should('exist') + .should('have.attr', 'aria-required', 'true') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText(/Affiliation/) + .should('exist') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText('Identifier Type', { exact: true }) + .should('exist') + .should('have.prop', 'tagName', 'SELECT') + .children('option') + .should('have.length', 9) + + cy.findByLabelText('Identifier', { exact: true }) + .should('exist') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText(`Add Author`).should('exist') + cy.findByLabelText(`Delete Author`).should('not.exist') + }) + + // Composed field + cy.findByText('Point of Contact') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText(/Name/) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText(/Affiliation/) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText(/E-mail/) + .should('exist') + .should('have.attr', 'aria-required', 'true') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Email) + + cy.findByLabelText(`Add Point of Contact`).should('exist') + cy.findByLabelText(`Delete Point of Contact`).should('not.exist') + }) + + // Composed field + cy.findByText('Description') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText(/Text/) + .should('exist') + .should('have.attr', 'aria-required', 'true') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Textbox) + + cy.findByLabelText(/Date/) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Date) + + cy.findByLabelText(`Add Description`).should('exist') + cy.findByLabelText(`Delete Description`).should('not.exist') + }) + + cy.findByText('Subject') + .should('exist') + .within(() => { + cy.get('[aria-label="Required input symbol"]').should('exist') + }) + + cy.findByLabelText(/Subject/).should('exist') + cy.findByLabelText(/Subject/).click() + cy.findByText('Subject1').should('exist') + cy.findByText('Subject2').should('exist') + cy.findByText('Subject3').should('exist') + cy.findByText('Subject4').should('exist') + + // Composed field + cy.findByText('Keyword') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText('Term', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText('Term URI', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.URL) + + cy.findByLabelText('Controlled Vocabulary Name', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText('Controlled Vocabulary URL', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.URL) + + cy.findByLabelText(`Add Keyword`).should('exist') + cy.findByLabelText(`Delete Keyword`).should('not.exist') + }) + + // Composed field + cy.findByText('Related Publication') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText('Citation', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Textbox) + + cy.findByLabelText('Identifier Type', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.prop', 'tagName', 'SELECT') + .children('option') + .should('have.length', 20) + + cy.findByLabelText('Identifier', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText('URL', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.URL) + + cy.findByLabelText(`Add Related Publication`).should('exist') + cy.findByLabelText(`Delete Related Publication`).should('not.exist') + }) + + cy.findByText('Notes').should('exist') + cy.findByLabelText(/Notes/) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Textbox) + cy.findByLabelText(/Notes/).should('have.prop', 'tagName', 'TEXTAREA') + }) + }) + + it('renders the correct form fields from Geospatial Metadata Block', () => { + // 2nd Accordion with Geospatial Metadata + cy.get('.accordion > :nth-child(2)').within(() => { + // Open accordion and wait for it to open + cy.get('.accordion-button').click() + cy.wait(300) + + cy.findByText(/Geospatial Metadata/i).should('exist') + + cy.findByLabelText('Geographic Unit') + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + // Composed field + cy.findByText('Geographic Coverage') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText(/Country \/ Nation/) + .should('exist') + .should('have.attr', 'aria-required', 'true') + .should('have.prop', 'tagName', 'SELECT') + .children('option') + .should('have.length', 250) + + cy.findByLabelText(/State \/ Province/) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText('City') + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText('Other') + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText(`Add Geographic Coverage`).should('not.exist') + }) + }) + }) + it('renders Save Dataset and Cancel buttons once', () => { + cy.findByRole('button', { name: 'Save Dataset' }).should('exist').should('have.length', 1) + + cy.findByRole('button', { name: 'Cancel' }).should('exist').should('have.length', 1) + }) + }) + + describe('renders the Citation and Geospatial Metadata Form Fields correctly on edit mode', () => { + beforeEach(() => { + cy.customMount( + + ) + }) + + it('renders the correct form fields from Citation Metadata Block', () => { + // 1st Accordion with Citation Metadata + cy.get('.accordion > :nth-child(1)').within(() => { + cy.findByText(/Citation Metadata/i).should('exist') + + cy.findByText('Title').should('exist') + cy.findByLabelText(/^Title/i) + .should('exist') + .should('have.attr', 'aria-required', 'true') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByText('Subtitle').should('exist') + cy.findByLabelText(/^Subtitle/i) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + // Primitive multiple + cy.findByText('Alternative Title') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText(/^Alternative Title/i) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText(`Add Alternative Title`).should('exist') + }) + + cy.findByText('Alternative URL').should('exist') + cy.findByLabelText(/^Alternative URL/i) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.URL) + + // Composed field + cy.findByText('Other Identifier') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText(/Agency/) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText('Identifier', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText(`Add Other Identifier`).should('exist') + cy.findByLabelText(`Delete Other Identifier`).should('not.exist') + }) + + // Composed field + cy.findByText('Author') + .should('exist') + .closest('.row') + .within(() => { + cy.findAllByLabelText(/Name/) + .should('exist') + .should('have.length', 2) + .should('have.attr', 'aria-required', 'true') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findAllByLabelText(/Name/).each( + (_$input, _$index, $inputList: HTMLInputElement[]) => { + const inputValues = Array.from($inputList).map((input) => input.value) + const expectedInputValues = ['Admin, Dataverse', 'Owner, Dataverse'] + expect(inputValues).to.deep.equal(expectedInputValues) + } + ) + + cy.findAllByLabelText(/Affiliation/) + .should('exist') + .should('have.length', 2) + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findAllByLabelText(/Affiliation/).each( + (_$input, _$index, $inputList: HTMLInputElement[]) => { + const inputValues = Array.from($inputList).map((input) => input.value) + const expectedInputValues = ['Dataverse.org', 'Dataverse.org'] + expect(inputValues).to.deep.equal(expectedInputValues) + } + ) + + cy.findAllByLabelText('Identifier Type', { exact: true }) + .should('exist') + .should('have.length', 2) + .should('have.attr', 'aria-required', 'false') + .should('have.prop', 'tagName', 'SELECT') + .children('option') + .should('have.length', 18) // 9 options each select + + cy.findAllByLabelText('Identifier Type', { exact: true }).each( + (_$input, _$index, $inputList: HTMLSelectElement[]) => { + const inputValues = Array.from($inputList).map((input) => input.value) + const expectedInputValues = ['ORCID', 'ORCID'] + expect(inputValues).to.deep.equal(expectedInputValues) + } + ) + + cy.findAllByLabelText('Identifier', { exact: true }) + .should('exist') + .should('have.length', 2) + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findAllByLabelText('Identifier', { exact: true }).each( + (_$input, _$index, $inputList: HTMLInputElement[]) => { + const inputValues = Array.from($inputList).map((input) => input.value) + const expectedInputValues = ['0000-0002-1825-1097', '0000-0032-1825-0098'] + expect(inputValues).to.deep.equal(expectedInputValues) + } + ) + + // As we have to groups of Author fields, we should have 2 Add Author buttons and 1 Delete Author button + cy.findAllByLabelText(`Add Author`).should('have.length', 2) + cy.findByLabelText(`Delete Author`).should('exist') + }) + + // Composed field + cy.findByText('Point of Contact') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText(/Name/) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', 'Admin, Dataverse') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText(/Affiliation/) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText(/E-mail/) + .should('exist') + .should('have.attr', 'aria-required', 'true') + .should('have.value', 'admin@dataverse.org') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Email) + + cy.findByLabelText(`Add Point of Contact`).should('exist') + cy.findByLabelText(`Delete Point of Contact`).should('not.exist') + }) + + // Composed field + cy.findByText('Description') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText(/Text/) + .should('exist') + .should('have.attr', 'aria-required', 'true') + .should( + 'have.value', + 'This text is *italic* and this is **bold**. Here is an image ![Alt text](https://picsum.photos/id/10/20/20) ' + ) + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Textbox) + + cy.findByLabelText(/Date/) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Date) + + cy.findByLabelText(`Add Description`).should('exist') + cy.findByLabelText(`Delete Description`).should('not.exist') + }) + + // Subject - Select multiple field + cy.findByText('Subject', { exact: true }) + .should('exist') + .within(() => { + cy.get('[aria-label="Required input symbol"]').should('exist') + }) + + cy.get('[id="citation.subject"]').as('subjectFieldInputButton') + cy.get('@subjectFieldInputButton').should('exist') + cy.get('@subjectFieldInputButton').click() + cy.findAllByRole('checkbox', { name: 'Subject1' }) + .should('be.checked') + .should('have.value', 'Subject1') + cy.findAllByRole('checkbox', { name: 'Subject2' }) + .should('be.checked') + .should('have.value', 'Subject2') + cy.findAllByRole('checkbox', { name: 'Subject3' }) + .should('not.be.checked') + .should('have.value', 'Subject3') + cy.findAllByRole('checkbox', { name: 'Subject4' }) + .should('not.be.checked') + .should('have.value', 'Subject4') + + // End Subject - Select multiple field + + // Composed field + cy.findByText('Keyword') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText('Term', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText('Term URI', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.URL) + + cy.findByLabelText('Controlled Vocabulary Name', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText('Controlled Vocabulary URL', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.URL) + + cy.findByLabelText(`Add Keyword`).should('exist') + cy.findByLabelText(`Delete Keyword`).should('not.exist') + }) + + // Composed field + cy.findByText('Topic Classification') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText('Term', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText('Controlled Vocabulary Name', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText('Controlled Vocabulary URL', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.URL) + + cy.findByLabelText(`Add Topic Classification`).should('exist') + cy.findByLabelText(`Delete Topic Classification`).should('not.exist') + }) + + // Composed field + cy.findByText('Related Publication') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText('Citation', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Textbox) + + cy.findByLabelText('Identifier Type', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.prop', 'tagName', 'SELECT') + .children('option') + .should('have.length', 20) + + cy.findByLabelText('Identifier', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText('URL', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.URL) + + cy.findByLabelText(`Add Related Publication`).should('exist') + cy.findByLabelText(`Delete Related Publication`).should('not.exist') + }) + + cy.findByText('Notes').should('exist') + cy.findByLabelText(/Notes/) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Textbox) + cy.findByLabelText(/Notes/).should('have.prop', 'tagName', 'TEXTAREA') + + // Language - Select multiple field + cy.findByText('Language', { exact: true }) + .should('exist') + .within(() => { + cy.get('[aria-label="Required input symbol"]').should('not.exist') + }) + + // Assert that all options are unchecked + cy.get('[id="citation.language"]').as('languageFieldInputButton') + cy.get('@languageFieldInputButton').should('exist') + cy.get('@languageFieldInputButton').click() + cy.findAllByRole('checkbox').should('not.be.checked') + cy.findAllByRole('checkbox').should('have.length', 187) + // End Language - Select multiple field + + // Composed field + cy.findByText('Producer') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText('Name', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText('Affiliation', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText('Abbreviated Name', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText('URL', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.URL) + + cy.findByLabelText('Logo URL', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.URL) + + cy.findAllByLabelText(`Add Producer`).should('exist') + cy.findByLabelText(`Delete Producer`).should('not.exist') + }) + + cy.findByText('Production Date').should('exist') + cy.findByLabelText('Production Date') + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Date) + + // Primitive multiple + cy.findByText('Production Location') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText(/^Production Location/i) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText(`Add Production Location`).should('exist') + }) + + // Composed field + cy.findByText('Contributor') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText('Type', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.prop', 'tagName', 'SELECT') + .children('option') + .should('have.length', 18) + + cy.findByLabelText('Name', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findAllByLabelText(`Add Contributor`).should('exist') + cy.findByLabelText(`Delete Contributor`).should('not.exist') + }) + + // Composed field + cy.findByText('Funding Information') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText('Agency', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText('Identifier', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findAllByLabelText(`Add Funding Information`).should('exist') + cy.findByLabelText(`Delete Funding Information`).should('not.exist') + }) + + // Composed field + cy.findByText('Distributor') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText('Name', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText('Affiliation', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText('Abbreviated Name', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText('URL', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.URL) + + cy.findByLabelText('Logo URL', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.URL) + + cy.findAllByLabelText(`Add Distributor`).should('exist') + cy.findByLabelText(`Delete Distributor`).should('not.exist') + }) + + cy.findByText('Distribution Date').should('exist') + cy.findByLabelText('Distribution Date') + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Date) + + cy.findByText('Depositor').should('exist') + cy.findByLabelText('Depositor') + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByText('Deposit Date').should('exist') + cy.findByLabelText('Deposit Date') + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Date) + + // Composed field + cy.findByText('Time Period') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText('Start Date', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Date) + + cy.findByLabelText('End Date', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Date) + + cy.findAllByLabelText(`Add Time Period`).should('exist') + cy.findByLabelText(`Delete Time Period`).should('not.exist') + }) + + // Composed field + cy.findByText('Date of Collection') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText('Start Date', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Date) + + cy.findByLabelText('End Date', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Date) + + cy.findAllByLabelText(`Add Date of Collection`).should('exist') + cy.findByLabelText(`Delete Date of Collection`).should('not.exist') + }) + + // Primitive multiple + cy.findByText('Data Type') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText(/^Data Type/i) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText(`Add Data Type`).should('exist') + }) + + // Composed field + cy.findByText('Series') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText('Name', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText('Information', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Textbox) + + cy.findAllByLabelText(`Add Series`).should('exist') + cy.findByLabelText(`Delete Series`).should('not.exist') + }) + + // Composed field + cy.findByText('Software') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText('Name', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText('Version', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findAllByLabelText(`Add Software`).should('exist') + cy.findByLabelText(`Delete Software`).should('not.exist') + }) + + // Primitive multiple + cy.findByText('Related Material') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText(/^Related Material/i) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Textbox) + + cy.findByLabelText(`Add Related Material`).should('exist') + }) + + // Primitive multiple + cy.findByText('Related Dataset') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText(/^Related Dataset/i) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Textbox) + + cy.findByLabelText(`Add Related Dataset`).should('exist') + }) + + cy.findByText('Other Reference') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText(/^Other Reference/i) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText(`Add Other Reference`).should('exist') + }) + + cy.findByText('Data Source') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText(/^Data Source/i) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Textbox) + + cy.findByLabelText(`Add Data Source`).should('exist') + }) + + cy.findByText('Origin of Historical Sources').should('exist') + cy.findByLabelText('Origin of Historical Sources') + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Textbox) + + cy.findByText('Characteristic of Sources').should('exist') + cy.findByLabelText('Characteristic of Sources') + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Textbox) + + cy.findByText('Documentation and Access to Sources').should('exist') + cy.findByLabelText('Documentation and Access to Sources') + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Textbox) + }) + }) + + it('renders the correct form fields from Geospatial Metadata Block', () => { + // 2nd Accordion with Geospatial Metadata + cy.get('.accordion > :nth-child(2)').within(() => { + // Open accordion and wait for it to open + cy.get('.accordion-button').click() + cy.wait(300) + + // Geographic Unit primitive text + cy.findByText('Geographic Unit').should('exist') + cy.findByLabelText('Geographic Unit') + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', 'km') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + // Composed field + cy.findByText('Geographic Coverage') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText(/Country \/ Nation/) + .should('exist') + .should('have.attr', 'aria-required', 'true') + .should('have.value', 'United States') + .should('have.prop', 'tagName', 'SELECT') + .children('option') + .should('have.length', 250) + + cy.findByLabelText(/State \/ Province/) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText('City') + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', 'Cambridge') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText('Other') + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText(`Add Geographic Coverage`).should('not.exist') + }) + + cy.findByText('Geographic Bounding Box') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText('Westernmost (Left) Longitude', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText('Easternmost (Right) Longitude', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText('Northernmost (Top) Latitude', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText('Southernmost (Bottom) Latitude', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + .should('have.value', '') + .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Text) + + cy.findByLabelText(`Add Geographic Bounding Box`).should('exist') + cy.findByLabelText(`Delete Geographic Bounding Box`).should('not.exist') + }) + }) + }) + + it('renders Save Changes and Cancel buttons twice, on top and bottom', () => { + cy.findAllByRole('button', { name: 'Save Changes' }).should('exist').should('have.length', 2) + + cy.findAllByRole('button', { name: 'Cancel' }).should('exist').should('have.length', 2) + }) + }) + + describe('should display required errors when submitting the form with required fields empty', () => { + it('on create mode', () => { + cy.customMount( + + ) + // Fill one non required field to undisable the Save button, is disabled if fields are not dirty + cy.findByLabelText(/^Notes/i).type('Some note') + + cy.findByText(/Save Dataset/i).click() + + cy.findByText('Title is required').should('exist') + cy.findByLabelText(/^Title/i).should('have.focus') + + cy.findByText('Author Name is required').should('exist') + cy.findByText('Point of Contact E-mail is required').should('exist') + cy.findByText('Description Text is required').should('exist') + cy.findByText('Subject is required').should('exist') + cy.findByText('Geographic Coverage Country / Nation is required').should('exist') + }) + + it('on edit mode', () => { + cy.customMount( + + ) + // Clear title field to undisable the Save button and unfill a required field that is already filled as it is in edit mode + cy.findByLabelText(/^Title/i).clear() + + cy.findAllByText(/Save Changes/i) + .first() + .click() + + cy.findByText('Title is required').should('exist') + cy.findByLabelText(/^Title/i).should('have.focus') + + cy.findByText('Author Name is required').should('not.exist') + cy.findByText('Point of Contact E-mail is required').should('not.exist') + cy.findByText('Description Text is required').should('not.exist') + cy.findByText('Subject is required').should('not.exist') + cy.findByText('Geographic Coverage Country / Nation is required').should('not.exist') + }) + }) + describe('should not display required errors when submitting the form with required fields filled', () => { + it('on create mode', () => { + cy.customMount( + + ) + fillRequiredFieldsOnCreate() + cy.findByText(/Save Dataset/i).click() + cy.findByText('Title is required').should('not.exist') + cy.findByText('Author Name is required').should('not.exist') + cy.findByText('Point of Contact E-mail is required').should('not.exist') + cy.findByText('Description Text is required').should('not.exist') + cy.findByText('Subject is required').should('not.exist') + cy.findByText('Geographic Coverage Country / Nation is required').should('not.exist') + + cy.findByText('Error').should('not.exist') + cy.findByText('Success!').should('exist') + }) + + it('on edit mode', () => { + cy.customMount( + + ) + cy.findByLabelText(/^Title/i) + .clear() + .type('New Title') + cy.findAllByText(/Save Changes/i) + .first() + .click() + + cy.findByText('Title is required').should('not.exist') + cy.findByText('Author Name is required').should('not.exist') + cy.findByText('Point of Contact E-mail is required').should('not.exist') + cy.findByText('Description Text is required').should('not.exist') + cy.findByText('Subject is required').should('not.exist') + cy.findByText('Geographic Coverage Country / Nation is required').should('not.exist') + + cy.findByText('Error').should('not.exist') + cy.findByText('Success!').should('exist') + }) + }) + + it('should show correct errors when filling inputs with invalid formats', () => { + datasetRepository.getByPersistentId = cy.stub().resolves(datasetWithAstroBlock) + metadataBlockInfoRepository.getDisplayedOnCreateByCollectionId = cy + .stub() + .resolves(metadataBlocksInfoOnCreateModeWithAstroBlock) + + cy.customMount( + + ) + cy.get('.accordion > :nth-child(1)').within(() => { + cy.findByText('Keyword') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText('Term URI', { exact: true }).type('html://test.com') + + cy.findByText('Keyword Term URI is not a valid URL').should('exist') + }) + + cy.findByText('Description') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText(/Date/).type('1990-23-23') + cy.findByText(/^Description Date is not a valid date./).should('exist') + }) + + cy.findByText('Point of Contact') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText(/^E-mail/i).type('test') + cy.findByText('Point of Contact E-mail is not a valid email').should('exist') + }) + }) + + // 3rd should be the Astronomy and Astrophysics Metadata block + cy.get('.accordion > :nth-child(3)').within(() => { + // Open accordion and wait for it to open + cy.get('.accordion-button').click() + cy.wait(300) + + cy.findByText('Object Density').should('exist') + cy.findByLabelText(/Object Density/) + .should('exist') + .type('30L') + cy.findByText('Object Density is not a valid float').should('exist') + + cy.findByText('Object Count').should('exist') + cy.findByLabelText(/Object Count/) + .should('exist') + .type('30.5') + cy.findByText('Object Count is not a valid integer').should('exist') + }) + }) + + it('should not show errors when filling inputs with valid formats', () => { + datasetRepository.getByPersistentId = cy.stub().resolves(datasetWithAstroBlock) + metadataBlockInfoRepository.getDisplayedOnCreateByCollectionId = cy + .stub() + .resolves(metadataBlocksInfoOnCreateModeWithAstroBlock) + + cy.customMount( + + ) + cy.get('.accordion > :nth-child(1)').within(() => { + cy.findByText('Keyword') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText('Term URI', { exact: true }).type('http://test.com') + + cy.findByText('Keyword Term URI is not a valid URL').should('not.exist') + }) + + cy.findByText('Description') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText(/Date/).type('1990-01-23') + cy.findByText(/^Description Date is not a valid date./).should('not.exist') + }) + + cy.findByText('Point of Contact') + .should('exist') + .closest('.row') + .within(() => { + cy.findByLabelText(/^E-mail/i).type('email@valid.com') + cy.findByText('Point of Contact E-mail is not a valid email').should('not.exist') + }) + }) + + // 3rd should be the Astronomy and Astrophysics Metadata block + cy.get('.accordion > :nth-child(3)').within(() => { + // Open accordion and wait for it to open + cy.get('.accordion-button').click() + cy.wait(300) + + cy.findByText('Object Density').should('exist') + cy.findByLabelText(/Object Density/) + .should('exist') + .type('30.5') + cy.findByText('Object Density is not a valid float').should('not.exist') + + cy.findByText('Object Count').should('exist') + cy.findByLabelText(/Object Count/) + .should('exist') + .type('30') + cy.findByText('Object Count is not a valid integer').should('not.exist') + + cy.findByText('Some Date').should('exist') + cy.findByLabelText(/Some Date/) + .should('exist') + .type('1990-01-23') + cy.findByText('Some Date is not a valid date').should('not.exist') + }) + }) + + it('pre-fills the form with user data', () => { + cy.mountAuthenticated( + + ) + cy.findByText('Author') + .closest('.row') + .within(() => { + cy.findByLabelText(/^Name/i).should('have.value', testUser.displayName) + }) + cy.findByText('Author') + .closest('.row') + .within(() => { + cy.findByLabelText(/^Affiliation/i).should('have.value', testUser.affiliation) + }) + cy.findByText('Point of Contact') + .closest('.row') + .within(() => { + cy.findByLabelText(/^Name/i).should('have.value', testUser.displayName) + }) + cy.findByText('Point of Contact') + .closest('.row') + .within(() => { + cy.findByLabelText(/^Affiliation/i).should('have.value', testUser.affiliation) + }) + cy.findByLabelText(/^E-mail/i).should('have.value', testUser.email) + }) + + it('shows the skeleton while loading', () => { + cy.customMount( + + ) + + cy.findByTestId('metadata-form-loading-skeleton').should('exist') + }) + + it('renders error message when getting collection metadata blocks info fails', () => { + metadataBlockInfoRepository.getDisplayedOnCreateByCollectionId = cy + .stub() + .rejects(new Error('some error')) + + cy.customMount( + + ) + + cy.findByText('Error').should('exist') + }) + + it('cancel button is clickable', () => { + cy.customMount( + + ) + + cy.findByText(/Cancel/i).click() + }) + + it('open closed accordion that has fields with errors on it and scrolls to the focused field', () => { + cy.customMount( + + ) + + // Fill one non required field to undisable the Save button, is disabled if fields are not dirty + cy.findByLabelText('Notes').type('Some note') + + // We are closing the accordion as it is open by default + cy.get(':nth-child(1) > .accordion-header > .accordion-button').click() + + cy.wait(300) + + cy.findByText('Save Dataset').click() + + cy.findByText('Title is required').should('exist') + cy.findByLabelText(/^Title/i) + .should('have.focus') + .should('be.visible') + }) + + describe('When dataset creation fails', () => { + it('should show create error message from the client-javascript client when the dataset creation fails', () => { + datasetRepository.create = cy + .stub() + .rejects(new Error('Error from the api javascript client')) + metadataBlockInfoRepository.getDisplayedOnCreateByCollectionId = cy + .stub() + .resolves(wrongCollectionMetadataBlocksInfo) + + cy.customMount( + + ) + + // Fill one non required field to undisable the Save button, is disabled if fields are not dirty + cy.findByLabelText('Title').type('Some note') + + cy.findByText(/Save Dataset/i).click() + + cy.findByText('Validation Error').should('exist') + cy.findByText(/Error from the api javascript client/).should('exist') + }) + + it('should only show field error part of the error message from the api when the dataset creation fails', () => { + datasetRepository.create = cy + .stub() + .rejects( + new Error( + 'Validation Failed: Point of Contact E-mail test@test.c is not a valid email address. (Invalid value:edu.harvard.iq.dataverse.DatasetFieldValueValue[ id=null ]).java.util.stream.ReferencePipeline$3@561b5200' + ) + ) + + cy.customMount( + + ) + // Fields are being send correctly, we are just forcing a create error to check if the error message is being displayed correctly + fillRequiredFieldsOnCreate() + + cy.findByText(/Save Dataset/i).click() + + cy.findByText('Validation Error').should('exist') + cy.findByText(/Point of Contact E-mail test@test.c is not a valid email address./).should( + 'exist' + ) + }) + + it('should show locale error message when the dataset creation fails with an unknown error message', () => { + datasetRepository.create = cy.stub().rejects('Some not expected error') + + cy.customMount( + + ) + + fillRequiredFieldsOnCreate() + + cy.findByText(/Save Dataset/i).click() + + cy.findByText('Validation Error').should('exist') + cy.findByText( + /Required fields were missed or there was a validation error. Please scroll down to see details./ + ).should('exist') + }) + }) + + describe('When dataset metadata update fails', () => { + it('should show edit error message from the client-javascript client when the dataset edition fails', () => { + datasetRepository.updateMetadata = cy + .stub() + .rejects(new Error('Error from the api javascript client')) + metadataBlockInfoRepository.getByColecctionId = cy + .stub() + .resolves(wrongCollectionMetadataBlocksInfo) + + cy.customMount( + + ) + + // Fill one non required field to undisable the Save button, is disabled if fields are not dirty + cy.findByLabelText('Title').type('Some note') + + cy.findAllByText(/Save Changes/i) + .first() + .click() + + cy.findByText('Validation Error').should('exist') + cy.findByText(/Error from the api javascript client/).should('exist') + }) + + it('should only show field error part of the error message from the api when the dataset edition fails', () => { + datasetRepository.updateMetadata = cy + .stub() + .rejects( + new Error( + 'Validation Failed: Point of Contact E-mail test@test.c is not a valid email address. (Invalid value:edu.harvard.iq.dataverse.DatasetFieldValueValue[ id=null ]).java.util.stream.ReferencePipeline$3@561b5200' + ) + ) + + cy.customMount( + + ) + + cy.findByLabelText(/^Title/i) + .clear() + .type('New Title') + + cy.findAllByText(/Save Changes/i) + .first() + .click() + + cy.findByText('Validation Error').should('exist') + cy.findByText(/Point of Contact E-mail test@test.c is not a valid email address./).should( + 'exist' + ) + }) + + it('should show locale error message when the dataset edition fails with an unknown error message', () => { + datasetRepository.updateMetadata = cy.stub().rejects('Some not expected error') + + cy.customMount( + + ) + + cy.findByLabelText(/^Title/i) + .clear() + .type('New Title') + + cy.findAllByText(/Save Changes/i) + .first() + .click() + + cy.findByText('Validation Error').should('exist') + cy.findByText( + /Required fields were missed or there was a validation error. Please scroll down to see details./ + ).should('exist') + }) + }) + + it('adds a new field and removes a field to a composed multiple field', () => { + cy.customMount( + + ) + + cy.findByText('Author') + .closest('.row') + .within(() => { + cy.findByLabelText(/^Name/i).type('Test foo name') + + cy.findByLabelText(`Add Author`).click() + + cy.findAllByLabelText(/^Name/i).should('have.length', 2) + + cy.findByLabelText(`Delete Author`).should('exist') + + cy.findByLabelText(`Delete Author`).click() + + cy.findByLabelText(`Delete Author`).should('not.exist') + + cy.findAllByLabelText(/^Name/i).should('have.length', 1) + }) + }) + + it('adds a new field and removes a field to a primitive multiple field', () => { + cy.customMount( + + ) + + cy.findByLabelText(/^Alternative Title/i).type('Alternative Title 1') + + cy.findByLabelText(`Add Alternative Title`).click() + + cy.findByLabelText(`Delete Alternative Title`).should('exist') + + cy.findByLabelText(`Delete Alternative Title`).click() + + cy.findByLabelText(`Delete Alternative Title`).should('not.exist') + }) + + it('should not submit the form when pressing enter key if submit button is not focused', () => { + cy.customMount( + + ) + + // We simulate using focusing on an input and pressing enter key + cy.findByLabelText(/^Title/i) + .focus() + .type('{enter}') + + // Validation error shouldn't be shown as form wasn't submitted + cy.findByText('Title is required').should('not.exist') + }) + it('should submit the form when pressing enter key if submit button is indeed focused', () => { + cy.customMount( + + ) + + // Type something so submit button is not disabled + cy.findByLabelText(/^Title/i).type('Some title') + + cy.findByText(/Save Dataset/i) + .focus() + .type('{enter}') + + // Validation error shouldn be shown as form was submitted + cy.findByText('Author Name is required').should('exist') + }) + + describe('should make field required if some of the siblings are filled and viceversa and show helper message', () => { + it('for a composed field multiple', () => { + cy.customMount( + + ) + + cy.findByText('Producer') + .should('exist') + .closest('.row') + .within(() => { + cy.findByText( + 'One or more of these fields may become required if you add to one or more of these optional fields.' + ).should('exist') + + cy.findByLabelText('Name', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + + cy.findByLabelText('Affiliation', { exact: true }) + .should('exist') + .type('something to trigger sibling Name field to become required') + + cy.findByLabelText(/^Name/).should('exist').should('have.attr', 'aria-required', 'true') + + cy.findByLabelText('Affiliation', { exact: true }).clear() + + cy.findByLabelText(/^Name/).should('exist').should('have.attr', 'aria-required', 'false') + }) + }) + it('for a composed field NOT multiple', () => { + metadataBlockInfoRepository.getByColecctionId = cy + .stub() + .resolves(metadataBlocksInfoOnCreateModeWithComposedNotMultipleField) + + cy.customMount( + + ) + + cy.get('.accordion > :nth-child(3)').within(() => { + // Open accordion and wait for it to open + cy.get('.accordion-button').click() + cy.wait(300) + cy.findByText('Producer') + .should('exist') + .closest('.row') + .within(() => { + cy.findByText( + 'One or more of these fields may become required if you add to one or more of these optional fields.' + ).should('exist') + + cy.findByLabelText('Name', { exact: true }) + .should('exist') + .should('have.attr', 'aria-required', 'false') + + cy.findByLabelText('Affiliation', { exact: true }) + .should('exist') + .type('something to trigger sibling Name field to become required') + + cy.findByLabelText(/^Name/).should('exist').should('have.attr', 'aria-required', 'true') + + cy.findByLabelText('Affiliation', { exact: true }).clear() + + cy.findByLabelText(/^Name/) + .should('exist') + .should('have.attr', 'aria-required', 'false') + }) + }) + }) + }) +}) diff --git a/tests/component/sections/shared/dataset-metadata-form/MetadataFieldsHelper.spec.ts b/tests/component/sections/shared/dataset-metadata-form/MetadataFieldsHelper.spec.ts new file mode 100644 index 000000000..c7919f066 --- /dev/null +++ b/tests/component/sections/shared/dataset-metadata-form/MetadataFieldsHelper.spec.ts @@ -0,0 +1,1417 @@ +import { DatasetMetadataBlocks } from '../../../../../src/dataset/domain/models/Dataset' +import { DatasetDTO } from '../../../../../src/dataset/domain/useCases/DTOs/DatasetDTO' +import { + MetadataBlockInfo, + MetadataBlockInfoWithMaybeValues +} from '../../../../../src/metadata-block-info/domain/models/MetadataBlockInfo' +import { + DatasetMetadataFormValues, + MetadataFieldsHelper +} from '../../../../../src/sections/shared/form/DatasetMetadataForm/MetadataFieldsHelper' + +const metadataBlocksInfo: MetadataBlockInfo[] = [ + { + id: 10, + name: 'foo', + displayName: 'Foo Metadata', + displayOnCreate: true, + metadataFields: { + someKeyWithoutDot: { + name: 'someKeyWithoutDot', + displayName: 'someKeyWithoutDot', + title: 'Some key without dout', + type: 'TEXT', + typeClass: 'primitive', + watermark: '', + description: 'foo', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOnCreate: true, + displayOrder: 0 + }, + controlledVocabularyNotMultiple: { + name: 'controlledVocabularyNotMultiple', + displayName: 'Foo', + title: 'Foo', + type: 'TEXT', + watermark: '', + description: 'Something here', + multiple: false, + isControlledVocabulary: true, + controlledVocabularyValues: ['Option1', 'Option2', 'Option3'], + displayFormat: '- #VALUE:', + isRequired: false, + displayOrder: 10, + typeClass: 'controlledVocabulary', + displayOnCreate: true + }, + controlledVocabularyMultiple: { + name: 'controlledVocabularyMultiple', + displayName: 'Foo', + title: 'Foo', + type: 'TEXT', + watermark: '', + description: 'Something here', + multiple: true, + isControlledVocabulary: true, + controlledVocabularyValues: ['Option1', 'Option2', 'Option3'], + displayFormat: '- #VALUE:', + isRequired: false, + displayOrder: 10, + typeClass: 'controlledVocabulary', + displayOnCreate: true + }, + 'primitive.text.not.multiple': { + name: 'primitive.text.not.multiple', + displayName: 'foo', + title: 'foo', + type: 'TEXT', + typeClass: 'primitive', + watermark: '', + description: 'foo', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOnCreate: true, + displayOrder: 0 + }, + 'primitive.text.multiple': { + name: 'primitive.text.multiple', + displayName: 'foo', + title: 'foo', + type: 'TEXT', + typeClass: 'primitive', + watermark: '', + description: 'foo', + multiple: true, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOnCreate: true, + displayOrder: 0 + }, + 'primitive.textbox.not.multiple': { + name: 'primitive.textbox.not.multiple', + displayName: 'foo', + title: 'foo', + type: 'TEXTBOX', + typeClass: 'primitive', + watermark: '', + description: 'foo', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOnCreate: true, + displayOrder: 0 + }, + 'primitive.textbox.multiple': { + name: 'primitive.textbox.multiple', + displayName: 'foo', + title: 'foo', + type: 'TEXTBOX', + typeClass: 'primitive', + watermark: '', + description: 'foo', + multiple: true, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOnCreate: true, + displayOrder: 0 + }, + 'primitive.float.not.multiple': { + name: 'primitive.float.not.multiple', + displayName: 'foo', + title: 'foo', + type: 'FLOAT', + watermark: 'Enter a floating-point number.', + description: 'foo', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOrder: 22, + typeClass: 'primitive', + displayOnCreate: false + }, + 'primitive.float.multiple': { + name: 'primitive.float.multiple', + displayName: 'foo', + title: 'foo', + type: 'FLOAT', + watermark: 'Enter a floating-point number.', + description: 'foo', + multiple: true, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOrder: 22, + typeClass: 'primitive', + displayOnCreate: false + }, + 'primitive.int.not.multiple': { + name: 'primitive.int.not.multiple', + displayName: 'foo', + title: 'foo', + type: 'INT', + watermark: 'Enter an integer.', + description: 'foo', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOrder: 18, + typeClass: 'primitive', + displayOnCreate: false + }, + 'primitive.int.multiple': { + name: 'primitive.int.multiple', + displayName: 'foo', + title: 'foo', + type: 'INT', + watermark: 'Enter an integer.', + description: 'foo', + multiple: true, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOrder: 18, + typeClass: 'primitive', + displayOnCreate: false + }, + 'primitive.date.not.multiple': { + name: 'primitive.date.not.multiple', + displayName: 'foo', + title: 'foo', + type: 'DATE', + watermark: 'YYYY-MM-DD', + description: + 'The date when the data were produced (not distributed, published, or archived)', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: false, + displayOrder: 42, + typeClass: 'primitive', + displayOnCreate: false + }, + 'primitive.date.multiple': { + name: 'primitive.date.multiple', + displayName: 'foo', + title: 'foo', + type: 'DATE', + watermark: 'YYYY-MM-DD', + description: + 'The date when the data were produced (not distributed, published, or archived)', + multiple: true, + isControlledVocabulary: false, + displayFormat: '', + isRequired: false, + displayOrder: 42, + typeClass: 'primitive', + displayOnCreate: false + }, + 'composed.field.multiple': { + name: 'composed.field.multiple', + displayName: 'Foo', + title: 'Foo', + type: 'NONE', + typeClass: 'compound', + watermark: '', + description: 'foo', + multiple: true, + isControlledVocabulary: false, + displayFormat: '', + isRequired: false, + displayOnCreate: true, + displayOrder: 12, + childMetadataFields: { + 'subfield.1': { + name: 'subfield.1', + displayName: 'bar', + title: 'Start', + type: 'DATE', + typeClass: 'primitive', + watermark: 'YYYY-MM-DD', + description: 'bar', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: false, + displayOnCreate: true, + displayOrder: 13 + }, + 'subfield.2': { + name: 'subfield.2', + displayName: 'bar', + title: 'End', + type: 'DATE', + typeClass: 'primitive', + watermark: 'YYYY-MM-DD', + description: 'bar', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: false, + displayOnCreate: true, + displayOrder: 14 + }, + someNestedKeyWithoutDot: { + name: 'someNestedKeyWithoutDot', + displayName: 'someNestedKeyWithoutDot', + title: 'Some nested key without dout', + type: 'TEXT', + typeClass: 'primitive', + watermark: '', + description: 'foo', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOnCreate: true, + displayOrder: 0 + }, + controlledVocabularyNotMultiple: { + name: 'controlledVocabularyNotMultiple', + displayName: 'Foo', + title: 'Foo', + type: 'TEXT', + watermark: '', + description: 'Something here', + multiple: false, + isControlledVocabulary: true, + controlledVocabularyValues: ['Option1', 'Option2', 'Option3'], + displayFormat: '- #VALUE:', + isRequired: false, + displayOrder: 10, + typeClass: 'controlledVocabulary', + displayOnCreate: true + } + } + }, + 'composed.field.not.multiple': { + name: 'composed.field.not.multiple', + displayName: 'Foo', + title: 'Foo', + type: 'NONE', + typeClass: 'compound', + watermark: '', + description: 'foo', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: false, + displayOnCreate: true, + displayOrder: 12, + childMetadataFields: { + 'subfield.1': { + name: 'subfield.1', + displayName: 'bar', + title: 'Start', + type: 'DATE', + typeClass: 'primitive', + watermark: 'YYYY-MM-DD', + description: 'bar', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: false, + displayOnCreate: true, + displayOrder: 13 + }, + 'subfield.2': { + name: 'subfield.2', + displayName: 'bar', + title: 'End', + type: 'DATE', + typeClass: 'primitive', + watermark: 'YYYY-MM-DD', + description: 'bar', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: false, + displayOnCreate: true, + displayOrder: 14 + }, + someNestedKeyWithoutDot: { + name: 'someNestedKeyWithoutDot', + displayName: 'someNestedKeyWithoutDot', + title: 'Some nested key without dout', + type: 'TEXT', + typeClass: 'primitive', + watermark: '', + description: 'foo', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOnCreate: true, + displayOrder: 0 + } + } + } + } + } +] + +const normalizedMetadataBlocksInfo: MetadataBlockInfo[] = [ + { + id: 10, + name: 'foo', + displayName: 'Foo Metadata', + displayOnCreate: true, + metadataFields: { + someKeyWithoutDot: { + name: 'someKeyWithoutDot', + displayName: 'someKeyWithoutDot', + title: 'Some key without dout', + type: 'TEXT', + typeClass: 'primitive', + watermark: '', + description: 'foo', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOnCreate: true, + displayOrder: 0 + }, + controlledVocabularyNotMultiple: { + name: 'controlledVocabularyNotMultiple', + displayName: 'Foo', + title: 'Foo', + type: 'TEXT', + watermark: '', + description: 'Something here', + multiple: false, + isControlledVocabulary: true, + controlledVocabularyValues: ['Option1', 'Option2', 'Option3'], + displayFormat: '- #VALUE:', + isRequired: false, + displayOrder: 10, + typeClass: 'controlledVocabulary', + displayOnCreate: true + }, + controlledVocabularyMultiple: { + name: 'controlledVocabularyMultiple', + displayName: 'Foo', + title: 'Foo', + type: 'TEXT', + watermark: '', + description: 'Something here', + multiple: true, + isControlledVocabulary: true, + controlledVocabularyValues: ['Option1', 'Option2', 'Option3'], + displayFormat: '- #VALUE:', + isRequired: false, + displayOrder: 10, + typeClass: 'controlledVocabulary', + displayOnCreate: true + }, + 'primitive.text.not.multiple': { + name: 'primitive/text/not/multiple', + displayName: 'foo', + title: 'foo', + type: 'TEXT', + typeClass: 'primitive', + watermark: '', + description: 'foo', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOnCreate: true, + displayOrder: 0 + }, + 'primitive.text.multiple': { + name: 'primitive/text/multiple', + displayName: 'foo', + title: 'foo', + type: 'TEXT', + typeClass: 'primitive', + watermark: '', + description: 'foo', + multiple: true, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOnCreate: true, + displayOrder: 0 + }, + 'primitive.textbox.not.multiple': { + name: 'primitive/textbox/not/multiple', + displayName: 'foo', + title: 'foo', + type: 'TEXTBOX', + typeClass: 'primitive', + watermark: '', + description: 'foo', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOnCreate: true, + displayOrder: 0 + }, + 'primitive.textbox.multiple': { + name: 'primitive/textbox/multiple', + displayName: 'foo', + title: 'foo', + type: 'TEXTBOX', + typeClass: 'primitive', + watermark: '', + description: 'foo', + multiple: true, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOnCreate: true, + displayOrder: 0 + }, + 'primitive.float.not.multiple': { + name: 'primitive/float/not/multiple', + displayName: 'foo', + title: 'foo', + type: 'FLOAT', + watermark: 'Enter a floating-point number.', + description: 'foo', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOrder: 22, + typeClass: 'primitive', + displayOnCreate: false + }, + 'primitive.float.multiple': { + name: 'primitive/float/multiple', + displayName: 'foo', + title: 'foo', + type: 'FLOAT', + watermark: 'Enter a floating-point number.', + description: 'foo', + multiple: true, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOrder: 22, + typeClass: 'primitive', + displayOnCreate: false + }, + 'primitive.int.not.multiple': { + name: 'primitive/int/not/multiple', + displayName: 'foo', + title: 'foo', + type: 'INT', + watermark: 'Enter an integer.', + description: 'foo', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOrder: 18, + typeClass: 'primitive', + displayOnCreate: false + }, + 'primitive.int.multiple': { + name: 'primitive/int/multiple', + displayName: 'foo', + title: 'foo', + type: 'INT', + watermark: 'Enter an integer.', + description: 'foo', + multiple: true, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOrder: 18, + typeClass: 'primitive', + displayOnCreate: false + }, + 'primitive.date.not.multiple': { + name: 'primitive/date/not/multiple', + displayName: 'foo', + title: 'foo', + type: 'DATE', + watermark: 'YYYY-MM-DD', + description: + 'The date when the data were produced (not distributed, published, or archived)', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: false, + displayOrder: 42, + typeClass: 'primitive', + displayOnCreate: false + }, + 'primitive.date.multiple': { + name: 'primitive/date/multiple', + displayName: 'foo', + title: 'foo', + type: 'DATE', + watermark: 'YYYY-MM-DD', + description: + 'The date when the data were produced (not distributed, published, or archived)', + multiple: true, + isControlledVocabulary: false, + displayFormat: '', + isRequired: false, + displayOrder: 42, + typeClass: 'primitive', + displayOnCreate: false + }, + 'composed.field.multiple': { + name: 'composed/field/multiple', + displayName: 'Foo', + title: 'Foo', + type: 'NONE', + typeClass: 'compound', + watermark: '', + description: 'foo', + multiple: true, + isControlledVocabulary: false, + displayFormat: '', + isRequired: false, + displayOnCreate: true, + displayOrder: 12, + childMetadataFields: { + 'subfield.1': { + name: 'subfield/1', + displayName: 'bar', + title: 'Start', + type: 'DATE', + typeClass: 'primitive', + watermark: 'YYYY-MM-DD', + description: 'bar', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: false, + displayOnCreate: true, + displayOrder: 13 + }, + 'subfield.2': { + name: 'subfield/2', + displayName: 'bar', + title: 'End', + type: 'DATE', + typeClass: 'primitive', + watermark: 'YYYY-MM-DD', + description: 'bar', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: false, + displayOnCreate: true, + displayOrder: 14 + }, + someNestedKeyWithoutDot: { + name: 'someNestedKeyWithoutDot', + displayName: 'someNestedKeyWithoutDot', + title: 'Some nested key without dout', + type: 'TEXT', + typeClass: 'primitive', + watermark: '', + description: 'foo', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOnCreate: true, + displayOrder: 0 + }, + controlledVocabularyNotMultiple: { + name: 'controlledVocabularyNotMultiple', + displayName: 'Foo', + title: 'Foo', + type: 'TEXT', + watermark: '', + description: 'Something here', + multiple: false, + isControlledVocabulary: true, + controlledVocabularyValues: ['Option1', 'Option2', 'Option3'], + displayFormat: '- #VALUE:', + isRequired: false, + displayOrder: 10, + typeClass: 'controlledVocabulary', + displayOnCreate: true + } + } + }, + 'composed.field.not.multiple': { + name: 'composed/field/not/multiple', + displayName: 'Foo', + title: 'Foo', + type: 'NONE', + typeClass: 'compound', + watermark: '', + description: 'foo', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: false, + displayOnCreate: true, + displayOrder: 12, + childMetadataFields: { + 'subfield.1': { + name: 'subfield/1', + displayName: 'bar', + title: 'Start', + type: 'DATE', + typeClass: 'primitive', + watermark: 'YYYY-MM-DD', + description: 'bar', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: false, + displayOnCreate: true, + displayOrder: 13 + }, + 'subfield.2': { + name: 'subfield/2', + displayName: 'bar', + title: 'End', + type: 'DATE', + typeClass: 'primitive', + watermark: 'YYYY-MM-DD', + description: 'bar', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: false, + displayOnCreate: true, + displayOrder: 14 + }, + someNestedKeyWithoutDot: { + name: 'someNestedKeyWithoutDot', + displayName: 'someNestedKeyWithoutDot', + title: 'Some nested key without dout', + type: 'TEXT', + typeClass: 'primitive', + watermark: '', + description: 'foo', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOnCreate: true, + displayOrder: 0 + } + } + } + } + } +] + +const normalizedMetadataBlocksInfoWithValues: MetadataBlockInfoWithMaybeValues[] = [ + { + id: 10, + name: 'foo', + displayName: 'Foo Metadata', + displayOnCreate: true, + metadataFields: { + someKeyWithoutDot: { + name: 'someKeyWithoutDot', + displayName: 'someKeyWithoutDot', + title: 'Some key without dout', + type: 'TEXT', + typeClass: 'primitive', + watermark: '', + description: 'foo', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOnCreate: true, + displayOrder: 0, + value: 'bar' + }, + controlledVocabularyNotMultiple: { + name: 'controlledVocabularyNotMultiple', + displayName: 'Foo', + title: 'Foo', + type: 'TEXT', + watermark: '', + description: 'Something here', + multiple: false, + isControlledVocabulary: true, + controlledVocabularyValues: ['Option1', 'Option2', 'Option3'], + displayFormat: '- #VALUE:', + isRequired: false, + displayOrder: 10, + typeClass: 'controlledVocabulary', + displayOnCreate: true, + value: 'Option2' + }, + controlledVocabularyMultiple: { + name: 'controlledVocabularyMultiple', + displayName: 'Foo', + title: 'Foo', + type: 'TEXT', + watermark: '', + description: 'Something here', + multiple: true, + isControlledVocabulary: true, + controlledVocabularyValues: ['Option1', 'Option2', 'Option3'], + displayFormat: '- #VALUE:', + isRequired: false, + displayOrder: 10, + typeClass: 'controlledVocabulary', + displayOnCreate: true, + value: ['Option1'] + }, + 'primitive.text.not.multiple': { + name: 'primitive/text/not/multiple', + displayName: 'foo', + title: 'foo', + type: 'TEXT', + typeClass: 'primitive', + watermark: '', + description: 'foo', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOnCreate: true, + displayOrder: 0, + value: 'foo' + }, + 'primitive.text.multiple': { + name: 'primitive/text/multiple', + displayName: 'foo', + title: 'foo', + type: 'TEXT', + typeClass: 'primitive', + watermark: '', + description: 'foo', + multiple: true, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOnCreate: true, + displayOrder: 0, + value: ['foo', 'bar'] + }, + 'primitive.textbox.not.multiple': { + name: 'primitive/textbox/not/multiple', + displayName: 'foo', + title: 'foo', + type: 'TEXTBOX', + typeClass: 'primitive', + watermark: '', + description: 'foo', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOnCreate: true, + displayOrder: 0, + value: '' + }, + 'primitive.textbox.multiple': { + name: 'primitive/textbox/multiple', + displayName: 'foo', + title: 'foo', + type: 'TEXTBOX', + typeClass: 'primitive', + watermark: '', + description: 'foo', + multiple: true, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOnCreate: true, + displayOrder: 0, + value: [] + }, + 'primitive.float.not.multiple': { + name: 'primitive/float/not/multiple', + displayName: 'foo', + title: 'foo', + type: 'FLOAT', + watermark: 'Enter a floating-point number.', + description: 'foo', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOrder: 22, + typeClass: 'primitive', + displayOnCreate: false, + value: '23.55' + }, + 'primitive.float.multiple': { + name: 'primitive/float/multiple', + displayName: 'foo', + title: 'foo', + type: 'FLOAT', + watermark: 'Enter a floating-point number.', + description: 'foo', + multiple: true, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOrder: 22, + typeClass: 'primitive', + displayOnCreate: false, + value: ['23.55', '45.55'] + }, + 'primitive.int.not.multiple': { + name: 'primitive/int/not/multiple', + displayName: 'foo', + title: 'foo', + type: 'INT', + watermark: 'Enter an integer.', + description: 'foo', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOrder: 18, + typeClass: 'primitive', + displayOnCreate: false, + value: '23' + }, + 'primitive.int.multiple': { + name: 'primitive/int/multiple', + displayName: 'foo', + title: 'foo', + type: 'INT', + watermark: 'Enter an integer.', + description: 'foo', + multiple: true, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOrder: 18, + typeClass: 'primitive', + displayOnCreate: false, + value: ['23', '45'] + }, + 'primitive.date.not.multiple': { + name: 'primitive/date/not/multiple', + displayName: 'foo', + title: 'foo', + type: 'DATE', + watermark: 'YYYY-MM-DD', + description: + 'The date when the data were produced (not distributed, published, or archived)', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: false, + displayOrder: 42, + typeClass: 'primitive', + displayOnCreate: false, + value: '2022-01-01' + }, + 'primitive.date.multiple': { + name: 'primitive/date/multiple', + displayName: 'foo', + title: 'foo', + type: 'DATE', + watermark: 'YYYY-MM-DD', + description: + 'The date when the data were produced (not distributed, published, or archived)', + multiple: true, + isControlledVocabulary: false, + displayFormat: '', + isRequired: false, + displayOrder: 42, + typeClass: 'primitive', + displayOnCreate: false, + value: ['2022-01-01', '2022-12-31'] + }, + 'composed.field.multiple': { + name: 'composed/field/multiple', + displayName: 'Foo', + title: 'Foo', + type: 'NONE', + typeClass: 'compound', + watermark: '', + description: 'foo', + multiple: true, + isControlledVocabulary: false, + displayFormat: '', + isRequired: false, + displayOnCreate: true, + displayOrder: 12, + childMetadataFields: { + 'subfield.1': { + name: 'subfield/1', + displayName: 'bar', + title: 'Start', + type: 'DATE', + typeClass: 'primitive', + watermark: 'YYYY-MM-DD', + description: 'bar', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: false, + displayOnCreate: true, + displayOrder: 13 + }, + 'subfield.2': { + name: 'subfield/2', + displayName: 'bar', + title: 'End', + type: 'DATE', + typeClass: 'primitive', + watermark: 'YYYY-MM-DD', + description: 'bar', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: false, + displayOnCreate: true, + displayOrder: 14 + }, + someNestedKeyWithoutDot: { + name: 'someNestedKeyWithoutDot', + displayName: 'someNestedKeyWithoutDot', + title: 'Some nested key without dout', + type: 'TEXT', + typeClass: 'primitive', + watermark: '', + description: 'foo', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOnCreate: true, + displayOrder: 0 + }, + controlledVocabularyNotMultiple: { + name: 'controlledVocabularyNotMultiple', + displayName: 'Foo', + title: 'Foo', + type: 'TEXT', + watermark: '', + description: 'Something here', + multiple: false, + isControlledVocabulary: true, + controlledVocabularyValues: ['Option1', 'Option2', 'Option3'], + displayFormat: '- #VALUE:', + isRequired: false, + displayOrder: 10, + typeClass: 'controlledVocabulary', + displayOnCreate: true + } + }, + value: [ + { + 'subfield/1': 'foo', + 'subfield/2': 'bar', + someNestedKeyWithoutDot: 'bar', + controlledVocabularyNotMultiple: '' + } + ] + }, + 'composed.field.not.multiple': { + name: 'composed/field/not/multiple', + displayName: 'Foo', + title: 'Foo', + type: 'NONE', + typeClass: 'compound', + watermark: '', + description: 'foo', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: false, + displayOnCreate: true, + displayOrder: 12, + childMetadataFields: { + 'subfield.1': { + name: 'subfield/1', + displayName: 'bar', + title: 'Start', + type: 'DATE', + typeClass: 'primitive', + watermark: 'YYYY-MM-DD', + description: 'bar', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: false, + displayOnCreate: true, + displayOrder: 13 + }, + 'subfield.2': { + name: 'subfield/2', + displayName: 'bar', + title: 'End', + type: 'DATE', + typeClass: 'primitive', + watermark: 'YYYY-MM-DD', + description: 'bar', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: false, + displayOnCreate: true, + displayOrder: 14 + }, + someNestedKeyWithoutDot: { + name: 'someNestedKeyWithoutDot', + displayName: 'someNestedKeyWithoutDot', + title: 'Some nested key without dout', + type: 'TEXT', + typeClass: 'primitive', + watermark: '', + description: 'foo', + multiple: false, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOnCreate: true, + displayOrder: 0 + } + }, + value: { + 'subfield/1': 'foo', + 'subfield/2': 'bar', + someNestedKeyWithoutDot: 'bar' + } + } + } + } +] + +const datasetMetadaBlocksCurrentValues: DatasetMetadataBlocks = [ + { + name: 'foo', + fields: { + someKeyWithoutDot: 'bar', + controlledVocabularyNotMultiple: 'Option2', + controlledVocabularyMultiple: ['Option1'], + 'primitive.text.not.multiple': 'foo', + 'primitive.text.multiple': ['foo', 'bar'], + 'primitive.textbox.not.multiple': '', + 'primitive.textbox.multiple': [], + 'primitive.float.not.multiple': '23.55', + 'primitive.float.multiple': ['23.55', '45.55'], + 'primitive.int.not.multiple': '23', + 'primitive.int.multiple': ['23', '45'], + 'primitive.date.not.multiple': '2022-01-01', + 'primitive.date.multiple': ['2022-01-01', '2022-12-31'], + 'composed.field.multiple': [ + { + 'subfield.1': 'foo', + 'subfield.2': 'bar', + someNestedKeyWithoutDot: 'bar', + controlledVocabularyNotMultiple: '' + } + ], + 'composed.field.not.multiple': { + 'subfield.1': 'foo', + 'subfield.2': 'bar', + someNestedKeyWithoutDot: 'bar' + } + } + } +] as unknown as DatasetMetadataBlocks + +const normalizedDatasetMetadataBlocksCurrentValues: DatasetMetadataBlocks = [ + { + name: 'foo', + fields: { + someKeyWithoutDot: 'bar', + controlledVocabularyNotMultiple: 'Option2', + controlledVocabularyMultiple: ['Option1'], + 'primitive/text/not/multiple': 'foo', + 'primitive/text/multiple': ['foo', 'bar'], + 'primitive/textbox/not/multiple': '', + 'primitive/textbox/multiple': [], + 'primitive/float/not/multiple': '23.55', + 'primitive/float/multiple': ['23.55', '45.55'], + 'primitive/int/not/multiple': '23', + 'primitive/int/multiple': ['23', '45'], + 'primitive/date/not/multiple': '2022-01-01', + 'primitive/date/multiple': ['2022-01-01', '2022-12-31'], + 'composed/field/multiple': [ + { + 'subfield/1': 'foo', + 'subfield/2': 'bar', + someNestedKeyWithoutDot: 'bar', + controlledVocabularyNotMultiple: '' + } + ], + 'composed/field/not/multiple': { + 'subfield/1': 'foo', + 'subfield/2': 'bar', + someNestedKeyWithoutDot: 'bar' + } + } + } +] as unknown as DatasetMetadataBlocks + +const formValuesWithValues: DatasetMetadataFormValues = { + foo: { + someKeyWithoutDot: 'bar', + controlledVocabularyNotMultiple: 'Option2', + controlledVocabularyMultiple: ['Option1'], + 'primitive/text/not/multiple': 'foo', + 'primitive/text/multiple': [{ value: 'foo' }, { value: 'bar' }], + 'primitive/textbox/not/multiple': '', + 'primitive/textbox/multiple': [], + 'primitive/float/not/multiple': '23.55', + 'primitive/float/multiple': [{ value: '23.55' }, { value: '45.55' }], + 'primitive/int/not/multiple': '23', + 'primitive/int/multiple': [{ value: '23' }, { value: '45' }], + 'primitive/date/not/multiple': '2022-01-01', + 'primitive/date/multiple': [{ value: '2022-01-01' }, { value: '2022-12-31' }], + 'composed/field/multiple': [ + { + 'subfield/1': 'foo', + 'subfield/2': 'bar', + someNestedKeyWithoutDot: 'bar', + controlledVocabularyNotMultiple: '' + } + ], + 'composed/field/not/multiple': { + 'subfield/1': 'foo', + 'subfield/2': 'bar', + someNestedKeyWithoutDot: 'bar' + } + } +} + +const formValuesEmptyValues: DatasetMetadataFormValues = { + foo: { + someKeyWithoutDot: '', + controlledVocabularyNotMultiple: '', + controlledVocabularyMultiple: [], + 'primitive/text/not/multiple': '', + 'primitive/text/multiple': [{ value: '' }], + 'primitive/textbox/not/multiple': '', + 'primitive/textbox/multiple': [{ value: '' }], + 'primitive/float/not/multiple': '', + 'primitive/float/multiple': [{ value: '' }], + 'primitive/int/not/multiple': '', + 'primitive/int/multiple': [{ value: '' }], + 'primitive/date/not/multiple': '', + 'primitive/date/multiple': [{ value: '' }], + 'composed/field/multiple': [ + { + 'subfield/1': '', + 'subfield/2': '', + someNestedKeyWithoutDot: '', + controlledVocabularyNotMultiple: '' + } + ], + 'composed/field/not/multiple': { + 'subfield/1': '', + 'subfield/2': '', + someNestedKeyWithoutDot: '' + } + } +} + +const formValuesWithValuesBackToDotKeys: DatasetMetadataFormValues = { + foo: { + someKeyWithoutDot: 'bar', + controlledVocabularyNotMultiple: 'Option2', + controlledVocabularyMultiple: ['Option1'], + 'primitive.text.not.multiple': 'foo', + 'primitive.text.multiple': [{ value: 'foo' }, { value: 'bar' }], + 'primitive.textbox.not.multiple': '', + 'primitive.textbox.multiple': [], + 'primitive.float.not.multiple': '23.55', + 'primitive.float.multiple': [{ value: '23.55' }, { value: '45.55' }], + 'primitive.int.not.multiple': '23', + 'primitive.int.multiple': [{ value: '23' }, { value: '45' }], + 'primitive.date.not.multiple': '2022-01-01', + 'primitive.date.multiple': [{ value: '2022-01-01' }, { value: '2022-12-31' }], + 'composed.field.multiple': [ + { + 'subfield.1': 'foo', + 'subfield.2': 'bar', + someNestedKeyWithoutDot: 'bar', + controlledVocabularyNotMultiple: '' + } + ], + 'composed.field.not.multiple': { + 'subfield.1': 'foo', + 'subfield.2': 'bar', + someNestedKeyWithoutDot: 'bar' + } + } +} + +const expectedDatasetDTO: DatasetDTO = { + metadataBlocks: [ + { + name: 'foo', + fields: { + someKeyWithoutDot: 'bar', + controlledVocabularyNotMultiple: 'Option2', + controlledVocabularyMultiple: ['Option1'], + 'primitive.text.not.multiple': 'foo', + 'primitive.text.multiple': ['foo', 'bar'], + 'primitive.float.not.multiple': '23.55', + 'primitive.float.multiple': ['23.55', '45.55'], + 'primitive.int.not.multiple': '23', + 'primitive.int.multiple': ['23', '45'], + 'primitive.date.not.multiple': '2022-01-01', + 'primitive.date.multiple': ['2022-01-01', '2022-12-31'], + 'composed.field.multiple': [ + { + 'subfield.1': 'foo', + 'subfield.2': 'bar', + someNestedKeyWithoutDot: 'bar' + } + ], + 'composed.field.not.multiple': { + 'subfield.1': 'foo', + 'subfield.2': 'bar', + someNestedKeyWithoutDot: 'bar' + } + } + } + ] +} + +describe('MetadataFieldsHelper', () => { + it('should replace names keys with dots with slashes from a metadata block info', () => { + const result = + MetadataFieldsHelper.replaceMetadataBlocksInfoDotNamesKeysWithSlash(metadataBlocksInfo) + expect(result).to.deep.equal(normalizedMetadataBlocksInfo) + }) + it('should replace dot keys with slashes from a Dataset current metadata blocks values ', () => { + const result = MetadataFieldsHelper.replaceDatasetMetadataBlocksCurrentValuesDotKeysWithSlash( + datasetMetadaBlocksCurrentValues + ) + + expect(result).to.deep.equal(normalizedDatasetMetadataBlocksCurrentValues) + }) + + it('should add field values to metadata blocks info', () => { + const result = MetadataFieldsHelper.addFieldValuesToMetadataBlocksInfo( + normalizedMetadataBlocksInfo, + normalizedDatasetMetadataBlocksCurrentValues + ) + + expect(result).to.deep.equal(normalizedMetadataBlocksInfoWithValues) + }) + + it('gets form default values from metadata blocks info with values', () => { + const result = MetadataFieldsHelper.getFormDefaultValues(normalizedMetadataBlocksInfoWithValues) + + expect(result).to.deep.equal(formValuesWithValues) + }) + + it('gets form default values from metadata blocks info without values', () => { + const result = MetadataFieldsHelper.getFormDefaultValues(normalizedMetadataBlocksInfo) + + expect(result).to.deep.equal(formValuesEmptyValues) + }) + + it('should replace form values keys with slash back to dots', () => { + const result = MetadataFieldsHelper.replaceSlashKeysWithDot(formValuesWithValues) + + expect(result).to.deep.equal(formValuesWithValuesBackToDotKeys) + }) + + it('should get dataset DTO from form values', () => { + const result = MetadataFieldsHelper.formatFormValuesToDatasetDTO( + formValuesWithValuesBackToDotKeys + ) + + expect(result).to.deep.equal(expectedDatasetDTO) + }) + + it('works all together, simulates a use in an edit mode form', () => { + // First we need to normalize the metadata blocks info, removing field names with dots and replacing them with slashes + const inTestNormalizedMetadataBlocksInfo = + MetadataFieldsHelper.replaceMetadataBlocksInfoDotNamesKeysWithSlash(metadataBlocksInfo) + + const inTestNormalizedDatasetMetadaBlocksCurrentValues = + MetadataFieldsHelper.replaceDatasetMetadataBlocksCurrentValuesDotKeysWithSlash( + datasetMetadaBlocksCurrentValues + ) + + const inTestNormalizedMetadataBlocksInfoWithValues = + MetadataFieldsHelper.addFieldValuesToMetadataBlocksInfo( + inTestNormalizedMetadataBlocksInfo, + inTestNormalizedDatasetMetadaBlocksCurrentValues + ) + + const inTestFormDefaultValues = MetadataFieldsHelper.getFormDefaultValues( + inTestNormalizedMetadataBlocksInfoWithValues + ) + const inTestFormDefaultValuesBackToDotKeys = + MetadataFieldsHelper.replaceSlashKeysWithDot(inTestFormDefaultValues) + + const inTestDatasetDTO = MetadataFieldsHelper.formatFormValuesToDatasetDTO( + formValuesWithValuesBackToDotKeys + ) + + expect(inTestNormalizedMetadataBlocksInfo).to.deep.equal(normalizedMetadataBlocksInfo) + expect(inTestNormalizedDatasetMetadaBlocksCurrentValues).to.deep.equal( + normalizedDatasetMetadataBlocksCurrentValues + ) + expect(inTestNormalizedMetadataBlocksInfoWithValues).to.deep.equal( + normalizedMetadataBlocksInfoWithValues + ) + expect(inTestFormDefaultValues).to.deep.equal(formValuesWithValues) + + expect(inTestFormDefaultValuesBackToDotKeys).to.deep.equal(formValuesWithValuesBackToDotKeys) + + expect(inTestDatasetDTO).to.deep.equal(expectedDatasetDTO) + }) + + describe('defineFieldName', () => { + const metadataBlockName = 'foo' + const fieldName = 'bar' + const compoundParentName = 'composed' + const fieldsArrayIndex = 0 + + it('defines basic field name correctly', () => { + const result = MetadataFieldsHelper.defineFieldName(fieldName, metadataBlockName) + + expect(result).to.equal(`${metadataBlockName}.${fieldName}`) + }) + + it('defines compound field name correctly', () => { + const result = MetadataFieldsHelper.defineFieldName( + fieldName, + metadataBlockName, + compoundParentName + ) + + expect(result).to.equal(`${metadataBlockName}.${compoundParentName}.${fieldName}`) + }) + + it('defines field array name correctly', () => { + const result = MetadataFieldsHelper.defineFieldName( + fieldName, + metadataBlockName, + undefined, + 0 + ) + + expect(result).to.equal(`${metadataBlockName}.${fieldName}.${fieldsArrayIndex}.value`) + }) + + it('defines compound field array name correctly', () => { + const result = MetadataFieldsHelper.defineFieldName( + fieldName, + metadataBlockName, + compoundParentName, + 0 + ) + + expect(result).to.equal( + `${metadataBlockName}.${compoundParentName}.${fieldsArrayIndex}.${fieldName}` + ) + }) + }) +}) diff --git a/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts b/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts index 282cc1791..1c1292f84 100644 --- a/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts +++ b/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts @@ -25,9 +25,12 @@ const expect = chai.expect function getCurrentDateInYYYYMMDDFormat() { const date = new Date() - return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String( - date.getDate() - ).padStart(2, '0')}` + const [year, month, day] = [ + date.getUTCFullYear(), + String(date.getUTCMonth() + 1).padStart(2, '0'), + String(date.getUTCDate()).padStart(2, '0') + ] + return `${year}-${month}-${day}` } function getPersistentIdUrl(persistentId: string) { diff --git a/tests/support/component.ts b/tests/support/component.ts index 12c3ff981..e51661096 100644 --- a/tests/support/component.ts +++ b/tests/support/component.ts @@ -16,6 +16,7 @@ // Import commands.js using ES2015 syntax: import './commands' import '@cypress/code-coverage/support' +import 'react-loading-skeleton/dist/skeleton.css' // Alternatively you can use CommonJS syntax: // require('./commands')