diff --git a/CHANGELOG.md b/CHANGELOG.md index 20a2f484d..93f7cdd36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ This changelog follows the principles of [Keep a Changelog](https://keepachangel ### Changed +- Use of the new `sourceLastUpdateTime` query parameter from update dataset and file metadata endpoints to support optimistic concurrency control during editing operations. See [Edit Dataset Metadata](https://guides.dataverse.org/en/6.8/api/native-api.html#edit-dataset-metadata) and [Updating File Metadata](https://guides.dataverse.org/en/6.8/api/native-api.html#updating-file-metadata) guides for more details. + ### Fixed ### Removed diff --git a/package-lock.json b/package-lock.json index 329cb1234..bf453b148 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@dnd-kit/sortable": "8.0.0", "@dnd-kit/utilities": "3.2.2", "@faker-js/faker": "7.6.0", - "@iqss/dataverse-client-javascript": "2.0.0-alpha.67", + "@iqss/dataverse-client-javascript": "2.0.0-alpha.76", "@iqss/dataverse-design-system": "*", "@istanbuljs/nyc-config-typescript": "1.0.2", "@tanstack/react-table": "8.9.2", @@ -1954,14 +1954,14 @@ }, "node_modules/@iqss/dataverse-client-javascript": { "name": "@IQSS/dataverse-client-javascript", - "version": "2.0.0-alpha.67", - "resolved": "https://npm.pkg.github.com/download/@IQSS/dataverse-client-javascript/2.0.0-alpha.67/0ee4f1c8e03eb3ef688d6b034e84003c9e155d3b", - "integrity": "sha512-uEAGtwXz7LYkBfWCBRktgb5d8oba6yPH9YWnVFhI40UqgdB1sQ/WWCDhZTn5LFsaQY+1XBzOoTjwrQ2HGz94og==", + "version": "2.0.0-alpha.76", + "resolved": "https://npm.pkg.github.com/download/@IQSS/dataverse-client-javascript/2.0.0-alpha.76/0bad3d2706d09bbc527bb622fc7abd6d72af7b72", + "integrity": "sha512-pOX+Ql53unePr/eClw8MZQcKPk+xVOR5Ag4Wi0Xdb9Z0GG+rYdOmVLwipkH70Dj0R0fC3BG3xAausT5B56yKhA==", "license": "MIT", "dependencies": { "@types/node": "^18.15.11", "@types/turndown": "^5.0.1", - "axios": "^1.7.2", + "axios": "^1.12.2", "turndown": "^7.1.2", "typescript": "^4.9.5" } diff --git a/package.json b/package.json index 5847fdfa4..28032a90f 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "@dnd-kit/sortable": "8.0.0", "@dnd-kit/utilities": "3.2.2", "@faker-js/faker": "7.6.0", - "@iqss/dataverse-client-javascript": "2.0.0-alpha.67", + "@iqss/dataverse-client-javascript": "2.0.0-alpha.76", "@iqss/dataverse-design-system": "*", "@istanbuljs/nyc-config-typescript": "1.0.2", "@tanstack/react-table": "8.9.2", diff --git a/src/dataset/domain/models/Dataset.ts b/src/dataset/domain/models/Dataset.ts index 30e10ea5f..19c9935a2 100644 --- a/src/dataset/domain/models/Dataset.ts +++ b/src/dataset/domain/models/Dataset.ts @@ -252,6 +252,12 @@ export class DatasetVersion { public readonly isInReview: boolean, public readonly latestVersionPublishingStatus: DatasetPublishingStatus, public readonly someDatasetVersionHasBeenReleased: boolean, + /** + * The timestamp of the last update to this dataset version. + * Format: ISO 8601 string (e.g., "2023-06-01T12:34:56Z"). + * Used for optimistic concurrency control to detect concurrent updates. + */ + public readonly lastUpdateTime: string, public readonly termsOfAccess?: TermsOfAccess, public readonly deaccessionNote?: string ) {} @@ -268,6 +274,7 @@ export class DatasetVersion { public readonly isInReview: boolean, public readonly latestVersionPublishingStatus: DatasetPublishingStatus, public readonly someDatasetVersionHasBeenReleased: boolean, + public readonly lastUpdateTime: string, public readonly termsOfAccess?: TermsOfAccess, public readonly deaccessionNote?: string ) { @@ -336,6 +343,7 @@ export class DatasetVersion { this.isInReview, this.latestVersionPublishingStatus, this.someDatasetVersionHasBeenReleased, + this.lastUpdateTime, this.termsOfAccess, this.deaccessionNote ) diff --git a/src/dataset/domain/repositories/DatasetRepository.ts b/src/dataset/domain/repositories/DatasetRepository.ts index b87314506..8c54c7fba 100644 --- a/src/dataset/domain/repositories/DatasetRepository.ts +++ b/src/dataset/domain/repositories/DatasetRepository.ts @@ -30,7 +30,7 @@ export interface DatasetRepository { updateMetadata: ( datasetId: string | number, datasetDTO: DatasetDTO, - internalVersionNumber: number + sourceLastUpdateTime?: string ) => Promise deaccession: ( datasetId: string | number, diff --git a/src/dataset/domain/useCases/updateDatasetMetadata.ts b/src/dataset/domain/useCases/updateDatasetMetadata.ts index d3a6c43c2..ec7e5cadd 100644 --- a/src/dataset/domain/useCases/updateDatasetMetadata.ts +++ b/src/dataset/domain/useCases/updateDatasetMetadata.ts @@ -5,10 +5,10 @@ export function updateDatasetMetadata( datasetRepository: DatasetRepository, datasetId: string | number, updatedDataset: DatasetDTO, - internalVersionNumber: number + sourceLastUpdateTime?: string ): Promise { return datasetRepository - .updateMetadata(datasetId, updatedDataset, internalVersionNumber) + .updateMetadata(datasetId, updatedDataset, sourceLastUpdateTime) .catch((error: Error) => { throw new Error(error.message) }) diff --git a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts index edb9d5a70..a147285e5 100644 --- a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts +++ b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts @@ -56,6 +56,7 @@ export class JSDatasetMapper { jsDataset.versionInfo, JSDatasetMapper.toDatasetTitle(jsDataset.metadataBlocks), jsDatasetCitation, + jsDataset.versionInfo.lastUpdateTime, jsDataset.publicationDate, jsDataset.termsOfUse?.termsOfAccess, jsDataset.versionInfo.deaccessionNote as string diff --git a/src/dataset/infrastructure/mappers/JSDatasetPreviewMapper.ts b/src/dataset/infrastructure/mappers/JSDatasetPreviewMapper.ts index f738592d4..a9bf65df4 100644 --- a/src/dataset/infrastructure/mappers/JSDatasetPreviewMapper.ts +++ b/src/dataset/infrastructure/mappers/JSDatasetPreviewMapper.ts @@ -12,7 +12,8 @@ export class JSDatasetPreviewMapper { jsDatasetPreview.versionId, jsDatasetPreview.versionInfo, jsDatasetPreview.title, - jsDatasetPreview.citation + jsDatasetPreview.citation, + jsDatasetPreview.versionInfo.lastUpdateTime ), releaseOrCreateDate: JSDatasetPreviewMapper.toPreviewDate(jsDatasetPreview.versionInfo), description: jsDatasetPreview.description, diff --git a/src/dataset/infrastructure/mappers/JSDatasetVersionMapper.ts b/src/dataset/infrastructure/mappers/JSDatasetVersionMapper.ts index 63b3fc8a3..ab12ee12f 100644 --- a/src/dataset/infrastructure/mappers/JSDatasetVersionMapper.ts +++ b/src/dataset/infrastructure/mappers/JSDatasetVersionMapper.ts @@ -15,6 +15,7 @@ export class JSDatasetVersionMapper { jsDatasetVersionInfo: JSDatasetVersionInfo, jsDatasetTitle: string, jsDatasetCitation: string, + jsDatasetLastUpdateTime: string, jsDatasetPublicationDate?: string, jsDatasettermsOfAccess?: TermsOfAccess, jsDatasetDeaccessionedNote?: string @@ -29,6 +30,7 @@ export class JSDatasetVersionMapper { false, // TODO Connect with dataset version isInReview this.toStatus(jsDatasetVersionInfo.state), this.toSomeDatasetVersionHasBeenReleased(jsDatasetVersionInfo, jsDatasetPublicationDate), + jsDatasetLastUpdateTime, jsDatasettermsOfAccess, jsDatasetDeaccessionedNote ) diff --git a/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts b/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts index 8d7d73d51..c398d6532 100644 --- a/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts +++ b/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts @@ -350,10 +350,10 @@ export class DatasetJSDataverseRepository implements DatasetRepository { updateMetadata( datasetId: string | number, updatedDataset: DatasetDTO, - internalVersionNumber: number + sourceLastUpdateTime?: string ): Promise { return updateDataset - .execute(datasetId, DatasetDTOMapper.toJSDatasetDTO(updatedDataset), internalVersionNumber) + .execute(datasetId, DatasetDTOMapper.toJSDatasetDTO(updatedDataset), sourceLastUpdateTime) .catch((error: WriteError) => { throw new Error(error.message) }) diff --git a/src/files/domain/repositories/FileRepository.ts b/src/files/domain/repositories/FileRepository.ts index 3f3dc32f3..587e8a1be 100644 --- a/src/files/domain/repositories/FileRepository.ts +++ b/src/files/domain/repositories/FileRepository.ts @@ -54,7 +54,11 @@ export interface FileRepository { delete: (fileId: number | string) => Promise replace: (fileId: number | string, uploadedFileDTO: UploadedFileDTO) => Promise getFixityAlgorithm: () => Promise - updateMetadata: (fileId: number | string, fileMetadata: FileMetadataDTO) => Promise + updateMetadata: ( + fileId: number | string, + fileMetadata: FileMetadataDTO, + sourceLastUpdateTime?: string + ) => Promise restrict: (fileId: number | string, restrictFileDTO: RestrictFileDTO) => Promise updateTabularTags: ( fileId: number | string, diff --git a/src/files/domain/useCases/editFileMetadata.ts b/src/files/domain/useCases/editFileMetadata.ts index 1a8f17c28..552d9f8fd 100644 --- a/src/files/domain/useCases/editFileMetadata.ts +++ b/src/files/domain/useCases/editFileMetadata.ts @@ -4,7 +4,8 @@ import { FileMetadataDTO } from '@/files/domain/useCases/DTOs/FileMetadataDTO' export function editFileMetadata( fileRepository: FileRepository, fileId: number | string, - fileMetadata: FileMetadataDTO + fileMetadata: FileMetadataDTO, + sourceLastUpdateTime?: string ): Promise { - return fileRepository.updateMetadata(fileId, fileMetadata) + return fileRepository.updateMetadata(fileId, fileMetadata, sourceLastUpdateTime) } diff --git a/src/files/infrastructure/FileJSDataverseRepository.ts b/src/files/infrastructure/FileJSDataverseRepository.ts index 91b50dcdd..29c287ce7 100644 --- a/src/files/infrastructure/FileJSDataverseRepository.ts +++ b/src/files/infrastructure/FileJSDataverseRepository.ts @@ -356,8 +356,12 @@ export class FileJSDataverseRepository implements FileRepository { .then((newFileIdentifier) => newFileIdentifier) } - updateMetadata(fileId: number | string, fileMetadata: FileMetadataDTO): Promise { - return updateFileMetadata.execute(fileId, fileMetadata) + updateMetadata( + fileId: number | string, + fileMetadata: FileMetadataDTO, + sourceLastUpdateTime?: string + ): Promise { + return updateFileMetadata.execute(fileId, fileMetadata, sourceLastUpdateTime) } // TODO - Not a priority but could be nice to implement this use case in js-dataverse when having time getFixityAlgorithm(): Promise { diff --git a/src/files/infrastructure/mappers/JSFileMapper.ts b/src/files/infrastructure/mappers/JSFileMapper.ts index d2ecb3704..7b1610f17 100644 --- a/src/files/infrastructure/mappers/JSFileMapper.ts +++ b/src/files/infrastructure/mappers/JSFileMapper.ts @@ -55,6 +55,7 @@ export class JSFileMapper { jsDataset.versionInfo, JSDatasetMapper.toDatasetTitle(jsDataset.metadataBlocks), datasetCitation, + jsDataset.versionInfo.lastUpdateTime, jsDataset.publicationDate, jsDataset.termsOfUse?.termsOfAccess ) diff --git a/src/sections/edit-dataset-metadata/EditDatasetMetadata.tsx b/src/sections/edit-dataset-metadata/EditDatasetMetadata.tsx index 4ba85e69a..2c42d7d3c 100644 --- a/src/sections/edit-dataset-metadata/EditDatasetMetadata.tsx +++ b/src/sections/edit-dataset-metadata/EditDatasetMetadata.tsx @@ -70,7 +70,7 @@ export const EditDatasetMetadata = ({ metadataBlockInfoRepository={metadataBlockInfoRepository} datasetPersistentID={dataset.persistentId} datasetMetadaBlocksCurrentValues={dataset.metadataBlocks} - datasetInternalVersionNumber={dataset.internalVersionNumber} + datasetLastUpdateTime={dataset.version.lastUpdateTime} /> diff --git a/src/sections/edit-file-metadata/EditFileMetadata.tsx b/src/sections/edit-file-metadata/EditFileMetadata.tsx index c72819a0e..04e11b855 100644 --- a/src/sections/edit-file-metadata/EditFileMetadata.tsx +++ b/src/sections/edit-file-metadata/EditFileMetadata.tsx @@ -73,6 +73,7 @@ export const EditFileMetadata = ({ fileId, fileRepository, referrer }: EditFileM editFileMetadataFormData={createEditFileMetadataFormData(file)} referrer={referrer} datasetPersistentId={file.hierarchy.parent?.persistentId} + datasetLastUpdateTime={file.datasetVersion.lastUpdateTime} /> diff --git a/src/sections/edit-file-metadata/EditFilesList.tsx b/src/sections/edit-file-metadata/EditFilesList.tsx index f6eba5868..3dc6311e6 100644 --- a/src/sections/edit-file-metadata/EditFilesList.tsx +++ b/src/sections/edit-file-metadata/EditFilesList.tsx @@ -31,13 +31,15 @@ type EditFilesListProps = { editFileMetadataFormData: EditFileMetadataFormData referrer: EditFileMetadataReferrer datasetPersistentId?: string + datasetLastUpdateTime: string } export const EditFilesList = ({ fileRepository, editFileMetadataFormData, referrer, - datasetPersistentId + datasetPersistentId, + datasetLastUpdateTime }: EditFilesListProps) => { const { t } = useTranslation('shared') const navigate = useNavigate() @@ -56,7 +58,8 @@ export const EditFilesList = ({ } const { submitForm, submissionStatus, submitError } = useSubmitFileMetadata( fileRepository, - onSubmitSucceed + onSubmitSucceed, + datasetLastUpdateTime ) const isSaving = submissionStatus === SubmissionStatus.IsSubmitting const handleCancel = () => navigate(-1) diff --git a/src/sections/edit-file-metadata/useSubmitFileMetadata.ts b/src/sections/edit-file-metadata/useSubmitFileMetadata.ts index 4704d21b8..d4142fe2c 100644 --- a/src/sections/edit-file-metadata/useSubmitFileMetadata.ts +++ b/src/sections/edit-file-metadata/useSubmitFileMetadata.ts @@ -27,7 +27,8 @@ type UseSubmitFileMetadataReturnType = export const useSubmitFileMetadata = ( fileRepository: FileRepository, - onSubmitSucceed: () => void + onSubmitSucceed: () => void, + datasetLastUpdateTime: string ) => { const [submissionStatus, setSubmissionStatus] = useState( SubmissionStatus.NotSubmitted @@ -45,7 +46,7 @@ export const useSubmitFileMetadata = ( directoryLabel: file.fileDir, label: file.fileName } - await editFileMetadata(fileRepository, file.id, fileMetadataDTO) + await editFileMetadata(fileRepository, file.id, fileMetadataDTO, datasetLastUpdateTime) } setSubmitError(null) diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/index.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/index.tsx index d50fe69f2..18cecfacb 100644 --- a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/index.tsx +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/index.tsx @@ -24,7 +24,7 @@ interface FormProps { metadataBlocksInfo: MetadataBlockInfo[] datasetRepository: DatasetRepository datasetPersistentID?: string - datasetInternalVersionNumber?: number + datasetLastUpdateTime?: string datasetTemplateInstructions?: DatasetTemplateInstruction[] } @@ -35,7 +35,7 @@ export const MetadataForm = ({ metadataBlocksInfo, datasetRepository, datasetPersistentID, - datasetInternalVersionNumber, + datasetLastUpdateTime, datasetTemplateInstructions }: FormProps) => { const { user } = useSession() @@ -57,7 +57,7 @@ export const MetadataForm = ({ datasetRepository, onSubmitDatasetError, datasetPersistentID, - datasetInternalVersionNumber + datasetLastUpdateTime ) usePrefillFieldsWithUserData({ mode, user, formDefaultValues, setValue }) diff --git a/src/sections/shared/form/DatasetMetadataForm/index.tsx b/src/sections/shared/form/DatasetMetadataForm/index.tsx index 107568919..5327be84c 100644 --- a/src/sections/shared/form/DatasetMetadataForm/index.tsx +++ b/src/sections/shared/form/DatasetMetadataForm/index.tsx @@ -18,7 +18,7 @@ type DatasetMetadataFormProps = datasetPersistentID?: never metadataBlockInfoRepository: MetadataBlockInfoRepository datasetMetadaBlocksCurrentValues?: never - datasetInternalVersionNumber?: never + datasetLastUpdateTime?: never datasetTemplate?: DatasetTemplate } | { @@ -28,7 +28,7 @@ type DatasetMetadataFormProps = datasetPersistentID: string metadataBlockInfoRepository: MetadataBlockInfoRepository datasetMetadaBlocksCurrentValues: DatasetMetadataBlocks - datasetInternalVersionNumber: number + datasetLastUpdateTime?: string datasetTemplate?: never } @@ -41,7 +41,7 @@ export const DatasetMetadataForm = ({ datasetPersistentID, metadataBlockInfoRepository, datasetMetadaBlocksCurrentValues, - datasetInternalVersionNumber, + datasetLastUpdateTime, datasetTemplate }: DatasetMetadataFormProps) => { const { setIsLoading } = useLoading() @@ -107,7 +107,7 @@ export const DatasetMetadataForm = ({ metadataBlocksInfo={metadataBlocksInfo} datasetRepository={datasetRepository} datasetPersistentID={datasetPersistentID} - datasetInternalVersionNumber={datasetInternalVersionNumber} + datasetLastUpdateTime={datasetLastUpdateTime} datasetTemplateInstructions={datasetTemplate?.instructions} /> ) diff --git a/src/sections/shared/form/DatasetMetadataForm/useSubmitDataset.ts b/src/sections/shared/form/DatasetMetadataForm/useSubmitDataset.ts index c10f6eafb..7a6afbe1d 100644 --- a/src/sections/shared/form/DatasetMetadataForm/useSubmitDataset.ts +++ b/src/sections/shared/form/DatasetMetadataForm/useSubmitDataset.ts @@ -39,7 +39,7 @@ export function useSubmitDataset( datasetRepository: DatasetRepository, onSubmitErrorCallback: () => void, datasetPersistentID?: string, - datasetInternalVersionNumber?: number + datasetLastUpdateTime?: string ): UseSubmitDatasetReturnType { const navigate = useNavigate() const { t } = useTranslation('shared', { keyPrefix: 'datasetMetadataForm' }) @@ -89,7 +89,7 @@ export function useSubmitDataset( datasetRepository, currentEditedDatasetPersistentID, formattedFormValues, - datasetInternalVersionNumber as number + datasetLastUpdateTime as string ) .then(() => { setSubmitError(null) diff --git a/src/stories/shared/dataset-metadata-form/DatasetMetadataForm.stories.tsx b/src/stories/shared/dataset-metadata-form/DatasetMetadataForm.stories.tsx index 367e0bf2c..efd1242e7 100644 --- a/src/stories/shared/dataset-metadata-form/DatasetMetadataForm.stories.tsx +++ b/src/stories/shared/dataset-metadata-form/DatasetMetadataForm.stories.tsx @@ -40,7 +40,7 @@ export const EditMode: Story = { metadataBlockInfoRepository={new MetadataBlockInfoMockRepository()} datasetPersistentID={datasetToEditMock.persistentId} datasetMetadaBlocksCurrentValues={datasetToEditMock.metadataBlocks} - datasetInternalVersionNumber={1} + datasetLastUpdateTime="2023-06-01T12:34:56Z" /> ) } diff --git a/tests/component/dataset/domain/models/DatasetMother.ts b/tests/component/dataset/domain/models/DatasetMother.ts index 661a2e6e9..24f1d697f 100644 --- a/tests/component/dataset/domain/models/DatasetMother.ts +++ b/tests/component/dataset/domain/models/DatasetMother.ts @@ -41,7 +41,8 @@ export class DatasetVersionMother { props?.isLatest ?? false, props?.isInReview ?? false, props?.latestVersionPublishingStatus ?? DatasetPublishingStatus.RELEASED, - props?.someDatasetVersionHasBeenReleased ?? faker.datatype.boolean() + props?.someDatasetVersionHasBeenReleased ?? faker.datatype.boolean(), + props?.lastUpdateTime ?? faker.date.recent().toISOString() ) } diff --git a/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts b/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts index ef11e886c..c4a38a696 100644 --- a/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts +++ b/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts @@ -44,7 +44,7 @@ const jsDataset = { majorNumber: 0, minorNumber: 0, createTime: new Date('2023-09-07T13:40:04.000Z'), - lastUpdateTime: new Date('2023-09-07T13:40:04.000Z'), + lastUpdateTime: '2023-09-07T13:40:04.000Z', releaseTime: undefined, deaccessionNote: undefined }, @@ -231,6 +231,7 @@ const expectedDataset = { majorNumber: 0 }, someDatasetVersionHasBeenReleased: false, + lastUpdateTime: '2023-09-07T13:40:04.000Z', termsOfAccess: termsOfAccess, deaccessionNote: undefined, citation: @@ -349,6 +350,7 @@ const expectedDatasetWithPublicationDate = { majorNumber: 0 }, someDatasetVersionHasBeenReleased: true, + lastUpdateTime: '2023-09-07T13:40:04.000Z', citation: 'Finch, Fiona, 2023, "Darwin\'s Finches", https://doi.org/10.5072/FK2/B4B2MJ, Root, DRAFT VERSION' }, @@ -464,6 +466,7 @@ const expectedDatasetWithNextVersionNumbers = { majorNumber: 0 }, someDatasetVersionHasBeenReleased: true, + lastUpdateTime: '2023-09-07T13:40:04.000Z', termsOfAccess: termsOfAccess, deaccessionNote: undefined, citation: @@ -585,6 +588,7 @@ const expectedDatasetAlternateVersion = { majorNumber: 0 }, someDatasetVersionHasBeenReleased: false, + lastUpdateTime: '2023-09-07T13:40:04.000Z', termsOfAccess: termsOfAccess, deaccessionNote: undefined, citation: diff --git a/tests/component/sections/edit-file-metadata/EditFilesList.spec.tsx b/tests/component/sections/edit-file-metadata/EditFilesList.spec.tsx index 12b64a79a..730ea5de7 100644 --- a/tests/component/sections/edit-file-metadata/EditFilesList.spec.tsx +++ b/tests/component/sections/edit-file-metadata/EditFilesList.spec.tsx @@ -1,10 +1,16 @@ -import { EditFilesList } from '@/sections/edit-file-metadata/EditFilesList' -import { FileMockRepository } from '@/stories/file/FileMockRepository' +import { + EditFileMetadataFormData, + EditFilesList +} from '@/sections/edit-file-metadata/EditFilesList' import { EditFileMetadataReferrer } from '@/sections/edit-file-metadata/EditFileMetadata' +import { FileMetadataDTO } from '@/files/domain/useCases/DTOs/FileMetadataDTO' +import { FileRepository } from '@/files/domain/repositories/FileRepository' + +const datasetLastUpdateTime = '2023-06-01T12:34:56Z' describe('EditFilesList Component', () => { - const fileRepository = new FileMockRepository() - const editFileMetadataFormData = { + const fileRepository: FileRepository = {} as FileRepository + const editFileMetadataFormData: EditFileMetadataFormData = { files: [ { id: 1, @@ -44,6 +50,7 @@ describe('EditFilesList Component', () => { } beforeEach(() => { cy.viewport(1200, 800) + fileRepository.updateMetadata = cy.stub().as('editFileMetadataStub').resolves() }) it('renders the form with file metadata', () => { cy.customMount( @@ -51,6 +58,7 @@ describe('EditFilesList Component', () => { fileRepository={fileRepository} editFileMetadataFormData={editFileMetadataFormData} referrer={EditFileMetadataReferrer.FILE} + datasetLastUpdateTime={datasetLastUpdateTime} /> ) @@ -71,6 +79,7 @@ describe('EditFilesList Component', () => { editFileMetadataFormData={multipleFilesFormData} referrer={EditFileMetadataReferrer.DATASET} datasetPersistentId="dataset-persistent-id" + datasetLastUpdateTime={datasetLastUpdateTime} /> ) @@ -97,6 +106,7 @@ describe('EditFilesList Component', () => { fileRepository={fileRepository} editFileMetadataFormData={editFileMetadataFormData} referrer={EditFileMetadataReferrer.FILE} + datasetLastUpdateTime={datasetLastUpdateTime} /> ) @@ -105,76 +115,96 @@ describe('EditFilesList Component', () => { }) it('submits the form', () => { - const editFileMetadataStub = cy.stub(fileRepository, 'updateMetadata').resolves() + fileRepository.updateMetadata = cy.stub().as('editFileMetadataStub').resolves() cy.customMount( ) + cy.findByLabelText(/File Name/) + .clear() + .type('newname.txt') + + cy.findByLabelText(/File Path/) + .clear() + .type('/newdir') + + cy.findByLabelText('Description').clear().type('New description') + cy.findByTestId('edit-file-metadata-form').submit() - cy.wrap(editFileMetadataStub).should('have.been.called') + + cy.get('@editFileMetadataStub').should((spy) => { + const editFileMetadataSpy = spy as unknown as Cypress.Agent + const fileMetadataDTO = editFileMetadataSpy.getCall(0).args[1] as FileMetadataDTO + const sourceLastUpdateTimeArg = editFileMetadataSpy.getCall(0).args[2] as string + + expect(fileMetadataDTO.label).to.equal('newname.txt') + expect(fileMetadataDTO.description).to.equal('New description') + expect(fileMetadataDTO.directoryLabel).to.equal('/newdir') + expect(sourceLastUpdateTimeArg).to.equal(datasetLastUpdateTime) + }) }) it('submits the form when referrer is Dataset', () => { - const editFileMetadataStub = cy.stub(fileRepository, 'updateMetadata').resolves() cy.customMount( ) cy.findByTestId('edit-file-metadata-form').submit() - cy.wrap(editFileMetadataStub).should('have.been.called') + cy.get('@editFileMetadataStub').should('have.been.called') }) it('handles error when submitting the form', () => { - const editFileMetadataStub = cy - .stub(fileRepository, 'updateMetadata') - .rejects(new Error('Error')) + fileRepository.updateMetadata = cy.stub().as('editFileMetadataStub').rejects(new Error('Error')) cy.customMount( ) cy.findByTestId('edit-file-metadata-form').submit() - cy.wrap(editFileMetadataStub).should('have.been.called') + cy.get('@editFileMetadataStub').should('have.been.called') cy.findByText('Error').should('exist') }) it('does not submit the form when pressing enter in the description textarea', () => { - const editFileMetadataStub = cy.stub(fileRepository, 'updateMetadata').resolves() cy.customMount( ) cy.findByLabelText('Description').type('{enter}') - cy.wrap(editFileMetadataStub).should('not.have.been.called') + cy.get('@editFileMetadataStub').should('not.have.been.called') }) it('does submit the form when pressing enter in the Save Changes button', () => { - const editFileMetadataStub = cy.stub(fileRepository, 'updateMetadata').resolves() cy.customMount( ) cy.findByRole('button', { name: 'Save Changes' }).type('{enter}') - cy.wrap(editFileMetadataStub).should('have.been.called') + cy.get('@editFileMetadataStub').should('have.been.called') }) }) diff --git a/tests/component/sections/shared/dataset-metadata-form/DatasetMetadataForm.spec.tsx b/tests/component/sections/shared/dataset-metadata-form/DatasetMetadataForm.spec.tsx index b1b6c0ac9..220b5026a 100644 --- a/tests/component/sections/shared/dataset-metadata-form/DatasetMetadataForm.spec.tsx +++ b/tests/component/sections/shared/dataset-metadata-form/DatasetMetadataForm.spec.tsx @@ -241,7 +241,7 @@ describe('DatasetMetadataForm', () => { metadataBlockInfoRepository={metadataBlockInfoRepository} datasetPersistentID={dataset.persistentId} datasetMetadaBlocksCurrentValues={dataset.metadataBlocks} - datasetInternalVersionNumber={dataset.internalVersionNumber} + datasetLastUpdateTime={dataset.version.lastUpdateTime} /> ) @@ -501,7 +501,7 @@ describe('DatasetMetadataForm', () => { metadataBlockInfoRepository={metadataBlockInfoRepository} datasetPersistentID={dataset.persistentId} datasetMetadaBlocksCurrentValues={dataset.metadataBlocks} - datasetInternalVersionNumber={dataset.internalVersionNumber} + datasetLastUpdateTime={dataset.version.lastUpdateTime} /> ) }) @@ -1255,7 +1255,7 @@ describe('DatasetMetadataForm', () => { metadataBlockInfoRepository={metadataBlockInfoRepository} datasetPersistentID={dataset.persistentId} datasetMetadaBlocksCurrentValues={dataset.metadataBlocks} - datasetInternalVersionNumber={dataset.internalVersionNumber} + datasetLastUpdateTime={dataset.version.lastUpdateTime} /> ) // Clear title field to undisable the Save button and unfill a required field that is already filled as it is in edit mode @@ -1307,7 +1307,7 @@ describe('DatasetMetadataForm', () => { metadataBlockInfoRepository={metadataBlockInfoRepository} datasetPersistentID={dataset.persistentId} datasetMetadaBlocksCurrentValues={dataset.metadataBlocks} - datasetInternalVersionNumber={dataset.internalVersionNumber} + datasetLastUpdateTime={dataset.version.lastUpdateTime} /> ) cy.findByLabelText(/^Title/i) @@ -1658,7 +1658,7 @@ describe('DatasetMetadataForm', () => { metadataBlockInfoRepository={metadataBlockInfoRepository} datasetPersistentID={dataset.persistentId} datasetMetadaBlocksCurrentValues={dataset.metadataBlocks} - datasetInternalVersionNumber={dataset.internalVersionNumber} + datasetLastUpdateTime={dataset.version.lastUpdateTime} /> ) @@ -1690,7 +1690,7 @@ describe('DatasetMetadataForm', () => { metadataBlockInfoRepository={metadataBlockInfoRepository} datasetPersistentID={dataset.persistentId} datasetMetadaBlocksCurrentValues={dataset.metadataBlocks} - datasetInternalVersionNumber={dataset.internalVersionNumber} + datasetLastUpdateTime={dataset.version.lastUpdateTime} /> ) @@ -1719,7 +1719,7 @@ describe('DatasetMetadataForm', () => { metadataBlockInfoRepository={metadataBlockInfoRepository} datasetPersistentID={dataset.persistentId} datasetMetadaBlocksCurrentValues={dataset.metadataBlocks} - datasetInternalVersionNumber={dataset.internalVersionNumber} + datasetLastUpdateTime={dataset.version.lastUpdateTime} /> ) @@ -1776,7 +1776,7 @@ describe('DatasetMetadataForm', () => { metadataBlockInfoRepository={metadataBlockInfoRepository} datasetPersistentID={dataset.persistentId} datasetMetadaBlocksCurrentValues={dataset.metadataBlocks} - datasetInternalVersionNumber={dataset.internalVersionNumber} + datasetLastUpdateTime={dataset.version.lastUpdateTime} /> ) @@ -1840,7 +1840,7 @@ describe('DatasetMetadataForm', () => { metadataBlockInfoRepository={metadataBlockInfoRepository} datasetPersistentID={dataset.persistentId} datasetMetadaBlocksCurrentValues={dataset.metadataBlocks} - datasetInternalVersionNumber={dataset.internalVersionNumber} + datasetLastUpdateTime={dataset.version.lastUpdateTime} /> ) @@ -1880,7 +1880,7 @@ describe('DatasetMetadataForm', () => { metadataBlockInfoRepository={metadataBlockInfoRepository} datasetPersistentID={dataset.persistentId} datasetMetadaBlocksCurrentValues={dataset.metadataBlocks} - datasetInternalVersionNumber={dataset.internalVersionNumber} + datasetLastUpdateTime={dataset.version.lastUpdateTime} /> ) diff --git a/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts b/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts index 7443de61a..9fd227a54 100644 --- a/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts +++ b/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts @@ -58,7 +58,7 @@ const termsOfUse = { termsOfAccessForRestrictedFiles: undefined } -const datasetData = (persistentId: string, versionId: number) => { +const datasetData = (persistentId: string, versionId: number, lastUpdateTime: string) => { return { license: { name: 'CC0 1.0', @@ -138,6 +138,7 @@ const datasetData = (persistentId: string, versionId: number) => { } ], someDatasetVersionHasBeenReleased: false, + lastUpdateTime, termsOfAccess: termsOfUse, deaccessionNote: undefined }, @@ -181,7 +182,11 @@ describe('Dataset JSDataverse Repository', () => { if (!dataset) { throw new Error('Dataset not found') } - const datasetExpected = datasetData(dataset.persistentId, dataset.version.id) + const datasetExpected = datasetData( + dataset.persistentId, + dataset.version.id, + dataset.version.lastUpdateTime + ) expect(dataset.license).to.deep.equal(datasetExpected.license) expect(dataset.metadataBlocks).to.deep.equal(datasetExpected.metadataBlocks) expect(dataset.summaryFields).to.deep.equal(datasetExpected.summaryFields) @@ -222,6 +227,7 @@ describe('Dataset JSDataverse Repository', () => { false, DatasetPublishingStatus.RELEASED, true, + dataset.version.lastUpdateTime, termsOfUse, undefined ) @@ -254,7 +260,11 @@ describe('Dataset JSDataverse Repository', () => { if (!dataset) { throw new Error('Dataset not found') } - const datasetExpected = datasetData(dataset.persistentId, dataset.version.id) + const datasetExpected = datasetData( + dataset.persistentId, + dataset.version.id, + dataset.version.lastUpdateTime + ) const newVersion = new DatasetVersion( dataset.version.id, "Darwin's Finches", @@ -266,6 +276,7 @@ describe('Dataset JSDataverse Repository', () => { false, DatasetPublishingStatus.RELEASED, true, + dataset.version.lastUpdateTime, termsOfUse, undefined ) @@ -289,7 +300,11 @@ describe('Dataset JSDataverse Repository', () => { if (!dataset) { throw new Error('Dataset not found') } - const datasetExpected = datasetData(dataset.persistentId, dataset.version.id) + const datasetExpected = datasetData( + dataset.persistentId, + dataset.version.id, + dataset.version.lastUpdateTime + ) expect(dataset.version.title).to.deep.equal(datasetExpected.title) expect(dataset.version).to.deep.equal(datasetExpected.version) @@ -304,7 +319,11 @@ describe('Dataset JSDataverse Repository', () => { if (!dataset) { throw new Error('Dataset not found') } - const datasetExpected = datasetData(dataset.persistentId, dataset.version.id) + const datasetExpected = datasetData( + dataset.persistentId, + dataset.version.id, + dataset.version.lastUpdateTime + ) expect(dataset.version.title).to.deep.equal(datasetExpected.title) expect(dataset.version).to.deep.equal(datasetExpected.version) @@ -366,7 +385,11 @@ describe('Dataset JSDataverse Repository', () => { if (!dataset) { throw new Error('Dataset not found') } - const datasetExpected = datasetData(dataset.persistentId, dataset.version.id) + const datasetExpected = datasetData( + dataset.persistentId, + dataset.version.id, + dataset.version.lastUpdateTime + ) expect(dataset.version.title).to.deep.equal(datasetExpected.title) expect(dataset.version.publishingStatus).to.equal(DatasetPublishingStatus.DEACCESSIONED) @@ -383,7 +406,11 @@ describe('Dataset JSDataverse Repository', () => { if (!dataset) { throw new Error('Dataset not found') } - const datasetExpected = datasetData(dataset.persistentId, dataset.version.id) + const datasetExpected = datasetData( + dataset.persistentId, + dataset.version.id, + dataset.version.lastUpdateTime + ) expect(dataset.version.title).to.deep.equal(datasetExpected.title)