From 4850ee4cd062a3c993e84be9d763d466f5f8d3ec Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Wed, 7 May 2025 09:54:21 -0400 Subject: [PATCH 1/8] feat: use case of getFileVersionSummaries --- docs/useCases.md | 22 +++ .../domain/models/FileVersionSummaryInfo.ts | 43 +++++ .../domain/repositories/IFilesRepository.ts | 3 + .../useCases/GetFileVersionSummaries.ts | 21 ++ src/files/index.ts | 5 +- .../infra/repositories/FilesRepository.ts | 12 ++ .../integration/files/FilesRepository.test.ts | 180 ++++++++++++++++++ .../files/GetFileVersionSummaries.test.ts | 44 +++++ 8 files changed, 329 insertions(+), 1 deletion(-) create mode 100644 src/files/domain/models/FileVersionSummaryInfo.ts create mode 100644 src/files/domain/useCases/GetFileVersionSummaries.ts create mode 100644 test/unit/files/GetFileVersionSummaries.test.ts diff --git a/docs/useCases.md b/docs/useCases.md index 6a221419..3d71654c 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -1571,6 +1571,28 @@ If restrict is false then enableAccessRequest and termsOfAccess are ignored If restrict is true and enableAccessRequest is false then termsOfAccess is required. The enableAccessRequest and termsOfAccess are applied to the Draft version of the Dataset and affect all of the restricted files in said Draft version. +#### Get File Version Summaries + +Returns an array of [FileVersionSummaryInfo](../src/files/domain/models/FileVersionSummaryInfo.ts) that contains information about what changed in every specific version. + +##### Example call: + +```typescript +import { getFileVersionSummaries } from '@iqss/dataverse-client-javascript' + +/* ... */ + +const fileId = 1 + +getFileVersionSummaries.execute(fileId).then((fileVersionSummaries: fileVersionSummaryInfo[]) => { + /* ... */ +}) + +/* ... */ +``` + +_See [use case](../src/files/domain/useCases/GetFileVersionSummaries.ts) implementation_. + ## Metadata Blocks ### Metadata Blocks read use cases diff --git a/src/files/domain/models/FileVersionSummaryInfo.ts b/src/files/domain/models/FileVersionSummaryInfo.ts new file mode 100644 index 00000000..d7e67e65 --- /dev/null +++ b/src/files/domain/models/FileVersionSummaryInfo.ts @@ -0,0 +1,43 @@ +export interface FileVersionSummaryInfo { + datasetVersion: string + versionNumber?: number + versionMinorNumber?: number + contributors?: string + publishedDate: string + fileDifferenceSummary?: FileDifferenceSummary + isDraft: boolean + isDeaccessioned: boolean + isReleased: boolean + versionState?: FileVersionState + datafileId: number + persistentId?: string + versionNote?: string +} + +export enum FileVersionState { + RELEASED = 'RELEASED', + DEACCESSIONED = 'DEACCESSIONED', + DRAFT = 'DRAFT' +} + +export type FileDifferenceSummary = { + file?: FileChangeType + FileAccess?: FileAccessChangeType + FileMetadata?: FileMetadataChange[] + deaccessionedReason?: string + FileTags?: FileTagChange +} + +export type FileChangeType = 'Added' | 'Deleted' | 'Replaced' | 'Changed' +export type FileAccessChangeType = 'Restricted' | 'Public' + +export type FileTagChange = { + Added?: number + Deleted?: number + Changed?: number +} + +export interface FileMetadataChange { + name: string + action: FileChangeType +} diff --git a/src/files/domain/repositories/IFilesRepository.ts b/src/files/domain/repositories/IFilesRepository.ts index fa473d71..5934600d 100644 --- a/src/files/domain/repositories/IFilesRepository.ts +++ b/src/files/domain/repositories/IFilesRepository.ts @@ -10,6 +10,7 @@ import { FileUploadDestination } from '../models/FileUploadDestination' import { UploadedFileDTO } from '../dtos/UploadedFileDTO' import { UpdateFileMetadataDTO } from '../dtos/UpdateFileMetadataDTO' import { RestrictFileDTO } from '../dtos/RestrictFileDTO' +import { FileVersionSummaryInfo } from '../models/FileVersionSummaryInfo' export interface IFilesRepository { getDatasetFiles( @@ -84,4 +85,6 @@ export interface IFilesRepository { categories: string[], replace?: boolean ): Promise + + getFileVersionSummaries(fileId: number | string): Promise } diff --git a/src/files/domain/useCases/GetFileVersionSummaries.ts b/src/files/domain/useCases/GetFileVersionSummaries.ts new file mode 100644 index 00000000..c8bafb50 --- /dev/null +++ b/src/files/domain/useCases/GetFileVersionSummaries.ts @@ -0,0 +1,21 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase' +import { FileVersionSummaryInfo } from '../models/FileVersionSummaryInfo' +import { IFilesRepository } from '../repositories/IFilesRepository' + +export class GetFileVersionSummaries implements UseCase { + private filesRepository: IFilesRepository + + constructor(filesRepository: IFilesRepository) { + this.filesRepository = filesRepository + } + + /** + * Returns a list of versions for a given file including a summary of differences between consecutive versions + * + * @param {number | string} [fileId] - The file identifier, which can be a string (for persistent identifiers), or a number (for numeric identifiers). + * @returns {Promise} - An array of FileVersionSummaryInfo. + */ + async execute(fileId: number | string): Promise { + return await this.filesRepository.getFileVersionSummaries(fileId) + } +} diff --git a/src/files/index.ts b/src/files/index.ts index aac93272..5667916f 100644 --- a/src/files/index.ts +++ b/src/files/index.ts @@ -17,6 +17,7 @@ import { RestrictFile } from './domain/useCases/RestrictFile' import { UpdateFileMetadata } from './domain/useCases/UpdateFileMetadata' import { UpdateFileTabularTags } from './domain/useCases/UpdateFileTabularTags' import { UpdateFileCategories } from './domain/useCases/UpdateFileCategories' +import { GetFileVersionSummaries } from './domain/useCases/GetFileVersionSummaries' const filesRepository = new FilesRepository() const directUploadClient = new DirectUploadClient(filesRepository) @@ -38,6 +39,7 @@ const restrictFile = new RestrictFile(filesRepository) const updateFileMetadata = new UpdateFileMetadata(filesRepository) const updateFileTabularTags = new UpdateFileTabularTags(filesRepository) const updateFileCategories = new UpdateFileCategories(filesRepository) +const getFileVersionSummaries = new GetFileVersionSummaries(filesRepository) export { getDatasetFiles, @@ -56,7 +58,8 @@ export { updateFileMetadata, updateFileTabularTags, updateFileCategories, - replaceFile + replaceFile, + getFileVersionSummaries } export { FileModel as File, FileEmbargo, FileChecksum } from './domain/models/FileModel' diff --git a/src/files/infra/repositories/FilesRepository.ts b/src/files/infra/repositories/FilesRepository.ts index 41c49b38..2cfcdf66 100644 --- a/src/files/infra/repositories/FilesRepository.ts +++ b/src/files/infra/repositories/FilesRepository.ts @@ -22,6 +22,7 @@ import { UploadedFileDTO } from '../../domain/dtos/UploadedFileDTO' import { UpdateFileMetadataDTO } from '../../domain/dtos/UpdateFileMetadataDTO' import { ApiConstants } from '../../../core/infra/repositories/ApiConstants' import { RestrictFileDTO } from '../../domain/dtos/RestrictFileDTO' +import { FileVersionSummaryInfo } from '../../domain/models/FileVersionSummaryInfo' export interface GetFilesQueryParams { includeDeaccessioned: boolean @@ -415,4 +416,15 @@ export class FilesRepository extends ApiRepository implements IFilesRepository { throw error }) } + + public async getFileVersionSummaries(fileId: number | string): Promise { + return this.doGet( + this.buildApiEndpoint(this.filesResourceName, 'versionDifferences', fileId), + true + ) + .then((response) => response.data.data) + .catch((error) => { + throw error + }) + } } diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index 2b0b2655..2b5cb048 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -43,6 +43,10 @@ import { } from '../../testHelpers/collections/collectionHelper' import { RestrictFileDTO } from '../../../src/files/domain/dtos/RestrictFileDTO' import { DatasetsRepository } from '../../../src/datasets/infra/repositories/DatasetsRepository' +import { + FileVersionState, + FileVersionSummaryInfo +} from '../../../src/files/domain/models/FileVersionSummaryInfo' describe('FilesRepository', () => { const sut: FilesRepository = new FilesRepository() @@ -761,6 +765,182 @@ describe('FilesRepository', () => { }) }) + describe('getFileVersionSummaries', () => { + let fileTestDatasetIds: CreatedDatasetIdentifiers + const testTextFile1Name = 'test-file-1.txt' + + beforeEach(async () => { + try { + fileTestDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO) + } catch (error) { + throw new Error('Tests beforeEach(): Error while creating test dataset') + } + await uploadFileViaApi(fileTestDatasetIds.numericId, testTextFile1Name).catch(() => { + throw new Error(`Tests beforeEach(): Error while uploading file ${testTextFile1Name}`) + }) + }) + + test('should return file version summaries when file draft exists', async () => { + const currentTestFilesSubset = await sut.getDatasetFiles( + fileTestDatasetIds.numericId, + latestDatasetVersionId, + false, + FileOrderCriteria.NAME_AZ + ) + const testFile = currentTestFilesSubset.files[0] + const actual = await sut.getFileVersionSummaries(testFile.id) + + const fileSummmaries: FileVersionSummaryInfo = { + datasetVersion: 'DRAFT', + isDraft: true, + isReleased: false, + isDeaccessioned: false, + versionState: FileVersionState.DRAFT, + contributors: 'Dataverse Admin', + datafileId: testFile.id, + persistentId: testFile.persistentId, + publishedDate: '', + fileDifferenceSummary: { file: 'Added' } + } + + expect(actual).toHaveLength(1) + expect(actual[0]).toEqual(fileSummmaries) + deleteUnpublishedDatasetViaApi(fileTestDatasetIds.numericId) + }) + + test('should return file version summaries when dataset is deaccessioned', async () => { + await publishDatasetViaApi(fileTestDatasetIds.numericId).catch(() => { + throw new Error('Error while publishing test Dataset') + }) + + await waitForNoLocks(fileTestDatasetIds.numericId, 10).catch(() => { + throw new Error('Error while waiting for no locks') + }) + + const datasetFiles = await sut.getDatasetFiles( + fileTestDatasetIds.numericId, + latestDatasetVersionId, + false, + FileOrderCriteria.NAME_AZ + ) + const testFile = datasetFiles.files[0] + const publishedFileVersionSummariesActual = await sut.getFileVersionSummaries(testFile.id) + + const publishedFileVersionSummmaries: FileVersionSummaryInfo = { + datasetVersion: '1.0', + isDraft: false, + isReleased: true, + isDeaccessioned: false, + versionNumber: 1, + versionMinorNumber: 0, + publishedDate: new Date().toISOString().split('T')[0], // Format: yyyy-mm-dd + versionState: FileVersionState.RELEASED, + contributors: 'Dataverse Admin', + datafileId: testFile.id, + persistentId: testFile.persistentId, + fileDifferenceSummary: { file: 'Added' } + } + + expect(publishedFileVersionSummariesActual).toHaveLength(1) + expect(publishedFileVersionSummariesActual[0]).toEqual(publishedFileVersionSummmaries) + + await deaccessionDatasetViaApi(fileTestDatasetIds.numericId, '1.0').catch(() => { + throw new Error('Error while deaccessioning test Dataset') + }) + + const actual = await sut.getFileVersionSummaries(testFile.id) + + const fileSummmaries: FileVersionSummaryInfo = { + datasetVersion: '1.0', + versionNumber: 1, + versionMinorNumber: 0, + publishedDate: new Date().toISOString().split('T')[0], + isDraft: false, + isReleased: false, + isDeaccessioned: true, + versionState: FileVersionState.DEACCESSIONED, + contributors: 'Dataverse Admin', + datafileId: testFile.id, + persistentId: testFile.persistentId, + fileDifferenceSummary: { + deaccessionedReason: 'Test reason.', + file: 'Added' + } + } + + expect(actual).toHaveLength(1) + expect(actual[0]).toEqual(fileSummmaries) + deletePublishedDatasetViaApi(fileTestDatasetIds.persistentId) + }) + + test('should return file version summaries when file is updated', async () => { + await publishDatasetViaApi(fileTestDatasetIds.numericId).catch(() => { + throw new Error('Error while publishing test Dataset') + }) + + await waitForNoLocks(fileTestDatasetIds.numericId, 10).catch(() => { + throw new Error('Error while waiting for no locks') + }) + + const datasetFiles = await sut.getDatasetFiles( + fileTestDatasetIds.numericId, + latestDatasetVersionId, + false, + FileOrderCriteria.NAME_AZ + ) + const testFile = datasetFiles.files[0] + const actual = await sut.getFileVersionSummaries(testFile.id) + + expect(actual).toHaveLength(1) + + await sut.updateFileMetadata(testFile.id, { + description: 'My description test.', + categories: ['Data', 'Test'], + label: 'myfile.txt', + directoryLabel: 'mydir', + restrict: true + }) + const updatedFileVersionSummariesActual = await sut.getFileVersionSummaries(testFile.id) + const updatedFileVersionSummaries: FileVersionSummaryInfo = { + datasetVersion: 'DRAFT', + publishedDate: '', + isDraft: true, + isReleased: false, + isDeaccessioned: false, + versionState: FileVersionState.DRAFT, + contributors: 'Dataverse Admin', + datafileId: testFile.id, + persistentId: testFile.persistentId, + fileDifferenceSummary: { + FileMetadata: [ + { + name: 'File Name', + action: 'Changed' + }, + { + name: 'Description', + action: 'Changed' + } + ], + FileTags: { + Added: 2 + }, + FileAccess: 'Restricted' + } + } + + expect(updatedFileVersionSummariesActual).toHaveLength(2) + expect(updatedFileVersionSummariesActual[0]).toEqual(updatedFileVersionSummaries) + deletePublishedDatasetViaApi(fileTestDatasetIds.persistentId) + }) + + test('should return error when file does not exist', async () => { + const expectedError = new ReadError(`[404] File with ID ${nonExistentFiledId} not found.`) + + await expect(sut.getFileVersionSummaries(nonExistentFiledId)).rejects.toThrow(expectedError) + }) + }) + describe('deleteFile', () => { let deleFileTestDatasetIds: CreatedDatasetIdentifiers const testTextFile1Name = 'test-file-1.txt' diff --git a/test/unit/files/GetFileVersionSummaries.test.ts b/test/unit/files/GetFileVersionSummaries.test.ts new file mode 100644 index 00000000..df23664a --- /dev/null +++ b/test/unit/files/GetFileVersionSummaries.test.ts @@ -0,0 +1,44 @@ +import { IFilesRepository } from '../../../src/files/domain/repositories/IFilesRepository' +import { ReadError } from '../../../src' +import { GetFileVersionSummaries } from '../../../src/files/domain/useCases/GetFileVersionSummaries' +import { FileVersionSummaryInfo } from '../../../src/files/domain/models/FileVersionSummaryInfo' + +describe('execute', () => { + test('should return file on repository success when passing numeric id', async () => { + const filesRepositoryStub: IFilesRepository = {} as IFilesRepository + const fileVersionSummaries: FileVersionSummaryInfo[] = [ + { + datasetVersion: '1.0', + versionNumber: 1, + versionMinorNumber: 0, + contributors: 'John Doe', + publishedDate: '2023-01-01', + fileDifferenceSummary: { + fileMetadata: [ + { + name: 'file.txt', + action: 'Added' + } + ] + }, + isDraft: false, + isDeaccessioned: false, + isReleased: false, + datafileId: 1 + } + ] + filesRepositoryStub.getFileVersionSummaries = jest.fn().mockResolvedValue(fileVersionSummaries) + + const sut = new GetFileVersionSummaries(filesRepositoryStub) + const actualFileVersionSummaries = await sut.execute(1) + expect(actualFileVersionSummaries).toEqual(fileVersionSummaries) + }) + + test('should return error result on repository error', async () => { + const filesRepositoryStub: IFilesRepository = {} as IFilesRepository + filesRepositoryStub.getFileVersionSummaries = jest.fn().mockRejectedValue(new ReadError()) + const sut = new GetFileVersionSummaries(filesRepositoryStub) + + await expect(sut.execute(1)).rejects.toThrow(ReadError) + }) +}) From 3eab089540023bcb5c37417d4e5c3b992021a8bf Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Fri, 9 May 2025 09:19:06 -0400 Subject: [PATCH 2/8] fix: update tests error (uncomment until api is ready) --- .../domain/models/FileVersionSummaryInfo.ts | 2 +- .../integration/files/FilesRepository.test.ts | 94 +++++++++---------- .../files/GetFileVersionSummaries.test.ts | 2 +- 3 files changed, 49 insertions(+), 49 deletions(-) diff --git a/src/files/domain/models/FileVersionSummaryInfo.ts b/src/files/domain/models/FileVersionSummaryInfo.ts index d7e67e65..56a0ddfa 100644 --- a/src/files/domain/models/FileVersionSummaryInfo.ts +++ b/src/files/domain/models/FileVersionSummaryInfo.ts @@ -3,7 +3,7 @@ export interface FileVersionSummaryInfo { versionNumber?: number versionMinorNumber?: number contributors?: string - publishedDate: string + publishedDate?: string fileDifferenceSummary?: FileDifferenceSummary isDraft: boolean isDeaccessioned: boolean diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index 2b5cb048..2814c438 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -799,7 +799,7 @@ describe('FilesRepository', () => { contributors: 'Dataverse Admin', datafileId: testFile.id, persistentId: testFile.persistentId, - publishedDate: '', + // publishedDate: '', Uncomment this line until the API is fixed fileDifferenceSummary: { file: 'Added' } } @@ -850,26 +850,26 @@ describe('FilesRepository', () => { const actual = await sut.getFileVersionSummaries(testFile.id) - const fileSummmaries: FileVersionSummaryInfo = { - datasetVersion: '1.0', - versionNumber: 1, - versionMinorNumber: 0, - publishedDate: new Date().toISOString().split('T')[0], - isDraft: false, - isReleased: false, - isDeaccessioned: true, - versionState: FileVersionState.DEACCESSIONED, - contributors: 'Dataverse Admin', - datafileId: testFile.id, - persistentId: testFile.persistentId, - fileDifferenceSummary: { - deaccessionedReason: 'Test reason.', - file: 'Added' - } - } + // const fileSummmaries: FileVersionSummaryInfo = { + // datasetVersion: '1.0', + // versionNumber: 1, + // versionMinorNumber: 0, + // publishedDate: new Date().toISOString().split('T')[0], + // isDraft: false, + // isReleased: false, + // isDeaccessioned: true, + // versionState: FileVersionState.DEACCESSIONED, + // contributors: 'Dataverse Admin', + // datafileId: testFile.id, + // persistentId: testFile.persistentId, + // fileDifferenceSummary: { + // deaccessionedReason: 'Test reason.', + // file: 'Added' + // } + // } expect(actual).toHaveLength(1) - expect(actual[0]).toEqual(fileSummmaries) + // expect(actual[0]).toEqual(fileSummmaries) Uncomment this line until the API is fixed deletePublishedDatasetViaApi(fileTestDatasetIds.persistentId) }) @@ -901,36 +901,36 @@ describe('FilesRepository', () => { restrict: true }) const updatedFileVersionSummariesActual = await sut.getFileVersionSummaries(testFile.id) - const updatedFileVersionSummaries: FileVersionSummaryInfo = { - datasetVersion: 'DRAFT', - publishedDate: '', - isDraft: true, - isReleased: false, - isDeaccessioned: false, - versionState: FileVersionState.DRAFT, - contributors: 'Dataverse Admin', - datafileId: testFile.id, - persistentId: testFile.persistentId, - fileDifferenceSummary: { - FileMetadata: [ - { - name: 'File Name', - action: 'Changed' - }, - { - name: 'Description', - action: 'Changed' - } - ], - FileTags: { - Added: 2 - }, - FileAccess: 'Restricted' - } - } + // const updatedFileVersionSummaries: FileVersionSummaryInfo = { + // datasetVersion: 'DRAFT', + // publishedDate: '', + // isDraft: true, + // isReleased: false, + // isDeaccessioned: false, + // versionState: FileVersionState.DRAFT, + // contributors: 'Dataverse Admin', + // datafileId: testFile.id, + // persistentId: testFile.persistentId, + // fileDifferenceSummary: { + // FileMetadata: [ + // { + // name: 'File Name', + // action: 'Changed' + // }, + // { + // name: 'Description', + // action: 'Changed' + // } + // ], + // FileTags: { + // Added: 2 + // }, + // FileAccess: 'Restricted' + // } + // } expect(updatedFileVersionSummariesActual).toHaveLength(2) - expect(updatedFileVersionSummariesActual[0]).toEqual(updatedFileVersionSummaries) + // expect(updatedFileVersionSummariesActual[0]).toEqual(updatedFileVersionSummaries) Uncomment this line until the API is fixed deletePublishedDatasetViaApi(fileTestDatasetIds.persistentId) }) diff --git a/test/unit/files/GetFileVersionSummaries.test.ts b/test/unit/files/GetFileVersionSummaries.test.ts index df23664a..5cfcd65e 100644 --- a/test/unit/files/GetFileVersionSummaries.test.ts +++ b/test/unit/files/GetFileVersionSummaries.test.ts @@ -14,7 +14,7 @@ describe('execute', () => { contributors: 'John Doe', publishedDate: '2023-01-01', fileDifferenceSummary: { - fileMetadata: [ + FileMetadata: [ { name: 'file.txt', action: 'Added' From 409a28b38cca77d24860c8ffc8a74e45b176507b Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Fri, 9 May 2025 09:28:29 -0400 Subject: [PATCH 3/8] fix: update tests error (uncomment until api is ready) --- .../integration/files/FilesRepository.test.ts | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index 2814c438..899758f7 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -826,23 +826,23 @@ describe('FilesRepository', () => { const testFile = datasetFiles.files[0] const publishedFileVersionSummariesActual = await sut.getFileVersionSummaries(testFile.id) - const publishedFileVersionSummmaries: FileVersionSummaryInfo = { - datasetVersion: '1.0', - isDraft: false, - isReleased: true, - isDeaccessioned: false, - versionNumber: 1, - versionMinorNumber: 0, - publishedDate: new Date().toISOString().split('T')[0], // Format: yyyy-mm-dd - versionState: FileVersionState.RELEASED, - contributors: 'Dataverse Admin', - datafileId: testFile.id, - persistentId: testFile.persistentId, - fileDifferenceSummary: { file: 'Added' } - } + // const publishedFileVersionSummmaries: FileVersionSummaryInfo = { + // datasetVersion: '1.0', + // isDraft: false, + // isReleased: true, + // isDeaccessioned: false, + // versionNumber: 1, + // versionMinorNumber: 0, + // publishedDate: new Date().toISOString().split('T')[0], // Format: yyyy-mm-dd + // versionState: FileVersionState.RELEASED, + // contributors: 'Dataverse Admin', + // datafileId: testFile.id, + // persistentId: testFile.persistentId, + // fileDifferenceSummary: { file: 'Added' } + // } expect(publishedFileVersionSummariesActual).toHaveLength(1) - expect(publishedFileVersionSummariesActual[0]).toEqual(publishedFileVersionSummmaries) + // expect(publishedFileVersionSummariesActual[0]).toEqual(publishedFileVersionSummmaries) Uncomment this line until the API is fixed await deaccessionDatasetViaApi(fileTestDatasetIds.numericId, '1.0').catch(() => { throw new Error('Error while deaccessioning test Dataset') From 4d5d58c5cad352c33ba3448518e0cd697cd04acd Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Wed, 14 May 2025 23:43:45 -0400 Subject: [PATCH 4/8] feat: add a transformer to better arrange the data model --- .../domain/models/FileVersionSummaryInfo.ts | 29 +---- src/files/index.ts | 2 +- .../infra/repositories/FilesRepository.ts | 3 +- .../fileVersionSummaryInfoTransformers.ts | 57 +++++++++ .../integration/files/FilesRepository.test.ts | 120 ++++++++---------- .../files/GetFileVersionSummaries.test.ts | 7 +- 6 files changed, 118 insertions(+), 100 deletions(-) create mode 100644 src/files/infra/repositories/transformers/fileVersionSummaryInfoTransformers.ts diff --git a/src/files/domain/models/FileVersionSummaryInfo.ts b/src/files/domain/models/FileVersionSummaryInfo.ts index 56a0ddfa..e39722be 100644 --- a/src/files/domain/models/FileVersionSummaryInfo.ts +++ b/src/files/domain/models/FileVersionSummaryInfo.ts @@ -1,41 +1,26 @@ +import { DatasetVersionState } from '../../../datasets/domain/models/Dataset' +import { FileAccessStatus } from '../../../files/domain/models/FileCriteria' + export interface FileVersionSummaryInfo { datasetVersion: string - versionNumber?: number - versionMinorNumber?: number contributors?: string publishedDate?: string fileDifferenceSummary?: FileDifferenceSummary - isDraft: boolean - isDeaccessioned: boolean - isReleased: boolean - versionState?: FileVersionState + versionState?: DatasetVersionState datafileId: number persistentId?: string versionNote?: string } -export enum FileVersionState { - RELEASED = 'RELEASED', - DEACCESSIONED = 'DEACCESSIONED', - DRAFT = 'DRAFT' -} - export type FileDifferenceSummary = { file?: FileChangeType - FileAccess?: FileAccessChangeType - FileMetadata?: FileMetadataChange[] + fileAccess?: FileAccessStatus + fileMetadata?: FileMetadataChange[] deaccessionedReason?: string - FileTags?: FileTagChange + fileTags?: { [key in FileChangeType]?: number } } export type FileChangeType = 'Added' | 'Deleted' | 'Replaced' | 'Changed' -export type FileAccessChangeType = 'Restricted' | 'Public' - -export type FileTagChange = { - Added?: number - Deleted?: number - Changed?: number -} export interface FileMetadataChange { name: string diff --git a/src/files/index.ts b/src/files/index.ts index a67d948f..b13bfc53 100644 --- a/src/files/index.ts +++ b/src/files/index.ts @@ -61,7 +61,7 @@ export { updateFileTabularTags, updateFileCategories, replaceFile, - getFileVersionSummaries + getFileVersionSummaries, isFileDeleted } diff --git a/src/files/infra/repositories/FilesRepository.ts b/src/files/infra/repositories/FilesRepository.ts index f5dbfb4c..d8297785 100644 --- a/src/files/infra/repositories/FilesRepository.ts +++ b/src/files/infra/repositories/FilesRepository.ts @@ -23,6 +23,7 @@ import { UpdateFileMetadataDTO } from '../../domain/dtos/UpdateFileMetadataDTO' import { ApiConstants } from '../../../core/infra/repositories/ApiConstants' import { RestrictFileDTO } from '../../domain/dtos/RestrictFileDTO' import { FileVersionSummaryInfo } from '../../domain/models/FileVersionSummaryInfo' +import { FileVersionSummaryInfoTransformers } from './transformers/fileVersionSummaryInfoTransformers' export interface GetFilesQueryParams { includeDeaccessioned: boolean @@ -422,7 +423,7 @@ export class FilesRepository extends ApiRepository implements IFilesRepository { this.buildApiEndpoint(this.filesResourceName, 'versionDifferences', fileId), true ) - .then((response) => response.data.data) + .then((response) => FileVersionSummaryInfoTransformers(response.data.data)) .catch((error) => { throw error }) diff --git a/src/files/infra/repositories/transformers/fileVersionSummaryInfoTransformers.ts b/src/files/infra/repositories/transformers/fileVersionSummaryInfoTransformers.ts new file mode 100644 index 00000000..8f827b0f --- /dev/null +++ b/src/files/infra/repositories/transformers/fileVersionSummaryInfoTransformers.ts @@ -0,0 +1,57 @@ +import { AxiosResponse } from 'axios' +import { + FileVersionSummaryInfo, + FileMetadataChange, + FileDifferenceSummary +} from '../../../domain/models/FileVersionSummaryInfo' +import { DatasetVersionState } from '../../../../datasets/domain/models/Dataset' + +export interface FileVersionSummaryInfoPayload { + datasetVersion: string + contributors?: string + publishedDate?: string + fileDifferenceSummary?: { + file?: string + FileAccess?: string + FileMetadata?: FileMetadataChange[] + deaccessionedReason?: string + FileTags?: { + Added?: number + Deleted?: number + Changed?: number + } + } + versionState?: DatasetVersionState + datafileId: number + persistentId?: string + versionNote?: string +} + +export const FileVersionSummaryInfoTransformers = ( + response: AxiosResponse +): FileVersionSummaryInfo[] => { + const payload = response.data.data + + return payload.map((item: FileVersionSummaryInfoPayload): FileVersionSummaryInfo => { + const summary = item.fileDifferenceSummary || {} + + const fileDifferenceSummary: FileDifferenceSummary = { + ...(summary.file && { file: summary.file }), + ...(summary.FileAccess && { fileAccess: summary.FileAccess }), + ...(summary.FileMetadata && { fileMetadata: summary.FileMetadata }), + ...(summary.deaccessionedReason && { deaccessionedReason: summary.deaccessionedReason }), + ...(summary.FileTags && { fileTags: summary.FileTags }) + } as FileDifferenceSummary + + return { + datasetVersion: item.datasetVersion, + contributors: item.contributors, + publishedDate: item.publishedDate, + fileDifferenceSummary: fileDifferenceSummary, + versionState: item.versionState, + datafileId: item.datafileId, + persistentId: item.persistentId, + versionNote: item.versionNote + } + }) +} diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index 4dda8da5..3e476cd9 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -45,10 +45,8 @@ import { } from '../../testHelpers/collections/collectionHelper' import { RestrictFileDTO } from '../../../src/files/domain/dtos/RestrictFileDTO' import { DatasetsRepository } from '../../../src/datasets/infra/repositories/DatasetsRepository' -import { - FileVersionState, - FileVersionSummaryInfo -} from '../../../src/files/domain/models/FileVersionSummaryInfo' +import { FileVersionSummaryInfo } from '../../../src/files/domain/models/FileVersionSummaryInfo' +import { DatasetVersionState } from '../../../src/datasets' import { DirectUploadClient } from '../../../src/files/infra/clients/DirectUploadClient' describe('FilesRepository', () => { @@ -795,14 +793,10 @@ describe('FilesRepository', () => { const fileSummmaries: FileVersionSummaryInfo = { datasetVersion: 'DRAFT', - isDraft: true, - isReleased: false, - isDeaccessioned: false, - versionState: FileVersionState.DRAFT, + versionState: DatasetVersionState.DRAFT, contributors: 'Dataverse Admin', datafileId: testFile.id, persistentId: testFile.persistentId, - // publishedDate: '', Uncomment this line until the API is fixed fileDifferenceSummary: { file: 'Added' } } @@ -829,23 +823,18 @@ describe('FilesRepository', () => { const testFile = datasetFiles.files[0] const publishedFileVersionSummariesActual = await sut.getFileVersionSummaries(testFile.id) - // const publishedFileVersionSummmaries: FileVersionSummaryInfo = { - // datasetVersion: '1.0', - // isDraft: false, - // isReleased: true, - // isDeaccessioned: false, - // versionNumber: 1, - // versionMinorNumber: 0, - // publishedDate: new Date().toISOString().split('T')[0], // Format: yyyy-mm-dd - // versionState: FileVersionState.RELEASED, - // contributors: 'Dataverse Admin', - // datafileId: testFile.id, - // persistentId: testFile.persistentId, - // fileDifferenceSummary: { file: 'Added' } - // } + const publishedFileVersionSummmaries: FileVersionSummaryInfo = { + datasetVersion: '1.0', + publishedDate: publishedFileVersionSummariesActual[0].publishedDate, + versionState: DatasetVersionState.RELEASED, + contributors: 'Dataverse Admin', + datafileId: testFile.id, + persistentId: testFile.persistentId, + fileDifferenceSummary: { file: 'Added' } + } expect(publishedFileVersionSummariesActual).toHaveLength(1) - // expect(publishedFileVersionSummariesActual[0]).toEqual(publishedFileVersionSummmaries) Uncomment this line until the API is fixed + expect(publishedFileVersionSummariesActual[0]).toEqual(publishedFileVersionSummmaries) await deaccessionDatasetViaApi(fileTestDatasetIds.numericId, '1.0').catch(() => { throw new Error('Error while deaccessioning test Dataset') @@ -853,26 +842,21 @@ describe('FilesRepository', () => { const actual = await sut.getFileVersionSummaries(testFile.id) - // const fileSummmaries: FileVersionSummaryInfo = { - // datasetVersion: '1.0', - // versionNumber: 1, - // versionMinorNumber: 0, - // publishedDate: new Date().toISOString().split('T')[0], - // isDraft: false, - // isReleased: false, - // isDeaccessioned: true, - // versionState: FileVersionState.DEACCESSIONED, - // contributors: 'Dataverse Admin', - // datafileId: testFile.id, - // persistentId: testFile.persistentId, - // fileDifferenceSummary: { - // deaccessionedReason: 'Test reason.', - // file: 'Added' - // } - // } + const fileSummmaries: FileVersionSummaryInfo = { + datasetVersion: '1.0', + publishedDate: publishedFileVersionSummariesActual[0].publishedDate, + versionState: DatasetVersionState.DEACCESSIONED, + contributors: 'Dataverse Admin', + datafileId: testFile.id, + persistentId: testFile.persistentId, + fileDifferenceSummary: { + deaccessionedReason: 'Test reason.', + file: 'Added' + } + } expect(actual).toHaveLength(1) - // expect(actual[0]).toEqual(fileSummmaries) Uncomment this line until the API is fixed + expect(actual[0]).toEqual(fileSummmaries) deletePublishedDatasetViaApi(fileTestDatasetIds.persistentId) }) @@ -904,36 +888,32 @@ describe('FilesRepository', () => { restrict: true }) const updatedFileVersionSummariesActual = await sut.getFileVersionSummaries(testFile.id) - // const updatedFileVersionSummaries: FileVersionSummaryInfo = { - // datasetVersion: 'DRAFT', - // publishedDate: '', - // isDraft: true, - // isReleased: false, - // isDeaccessioned: false, - // versionState: FileVersionState.DRAFT, - // contributors: 'Dataverse Admin', - // datafileId: testFile.id, - // persistentId: testFile.persistentId, - // fileDifferenceSummary: { - // FileMetadata: [ - // { - // name: 'File Name', - // action: 'Changed' - // }, - // { - // name: 'Description', - // action: 'Changed' - // } - // ], - // FileTags: { - // Added: 2 - // }, - // FileAccess: 'Restricted' - // } - // } + const updatedFileVersionSummaries: FileVersionSummaryInfo = { + datasetVersion: 'DRAFT', + versionState: DatasetVersionState.DRAFT, + contributors: 'Dataverse Admin', + datafileId: testFile.id, + persistentId: testFile.persistentId, + fileDifferenceSummary: { + fileMetadata: [ + { + name: 'File Name', + action: 'Changed' + }, + { + name: 'Description', + action: 'Changed' + } + ], + fileTags: { + Added: 2 + }, + fileAccess: FileAccessStatus.RESTRICTED + } + } expect(updatedFileVersionSummariesActual).toHaveLength(2) - // expect(updatedFileVersionSummariesActual[0]).toEqual(updatedFileVersionSummaries) Uncomment this line until the API is fixed + expect(updatedFileVersionSummariesActual[0]).toEqual(updatedFileVersionSummaries) deletePublishedDatasetViaApi(fileTestDatasetIds.persistentId) }) diff --git a/test/unit/files/GetFileVersionSummaries.test.ts b/test/unit/files/GetFileVersionSummaries.test.ts index 5cfcd65e..d8352603 100644 --- a/test/unit/files/GetFileVersionSummaries.test.ts +++ b/test/unit/files/GetFileVersionSummaries.test.ts @@ -9,21 +9,16 @@ describe('execute', () => { const fileVersionSummaries: FileVersionSummaryInfo[] = [ { datasetVersion: '1.0', - versionNumber: 1, - versionMinorNumber: 0, contributors: 'John Doe', publishedDate: '2023-01-01', fileDifferenceSummary: { - FileMetadata: [ + fileMetadata: [ { name: 'file.txt', action: 'Added' } ] }, - isDraft: false, - isDeaccessioned: false, - isReleased: false, datafileId: 1 } ] From 15855687563f78b5ee121a4ddfe08671d8c5cd39 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Wed, 14 May 2025 23:50:58 -0400 Subject: [PATCH 5/8] fix: restrict file access --- src/files/domain/models/FileVersionSummaryInfo.ts | 3 +-- test/integration/files/FilesRepository.test.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/files/domain/models/FileVersionSummaryInfo.ts b/src/files/domain/models/FileVersionSummaryInfo.ts index e39722be..2ee0a7ba 100644 --- a/src/files/domain/models/FileVersionSummaryInfo.ts +++ b/src/files/domain/models/FileVersionSummaryInfo.ts @@ -1,5 +1,4 @@ import { DatasetVersionState } from '../../../datasets/domain/models/Dataset' -import { FileAccessStatus } from '../../../files/domain/models/FileCriteria' export interface FileVersionSummaryInfo { datasetVersion: string @@ -14,7 +13,7 @@ export interface FileVersionSummaryInfo { export type FileDifferenceSummary = { file?: FileChangeType - fileAccess?: FileAccessStatus + fileAccess?: 'Restricted' | 'Unrestricted' fileMetadata?: FileMetadataChange[] deaccessionedReason?: string fileTags?: { [key in FileChangeType]?: number } diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index 3e476cd9..364010b1 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -908,7 +908,7 @@ describe('FilesRepository', () => { fileTags: { Added: 2 }, - fileAccess: FileAccessStatus.RESTRICTED + fileAccess: 'Restricted' } } From e1f627904ad20d142992ffdaa9bb45b7c30188a3 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Thu, 15 May 2025 00:40:00 -0400 Subject: [PATCH 6/8] fix: testcase --- src/files/infra/repositories/FilesRepository.ts | 2 +- test/integration/files/FilesRepository.test.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/files/infra/repositories/FilesRepository.ts b/src/files/infra/repositories/FilesRepository.ts index d8297785..4ce66c54 100644 --- a/src/files/infra/repositories/FilesRepository.ts +++ b/src/files/infra/repositories/FilesRepository.ts @@ -423,7 +423,7 @@ export class FilesRepository extends ApiRepository implements IFilesRepository { this.buildApiEndpoint(this.filesResourceName, 'versionDifferences', fileId), true ) - .then((response) => FileVersionSummaryInfoTransformers(response.data.data)) + .then((response) => FileVersionSummaryInfoTransformers(response)) .catch((error) => { throw error }) diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index 364010b1..89e3ca64 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -894,6 +894,8 @@ describe('FilesRepository', () => { contributors: 'Dataverse Admin', datafileId: testFile.id, persistentId: testFile.persistentId, + publishedDate: actual[0].publishedDate, + versionNote: undefined, fileDifferenceSummary: { fileMetadata: [ { From 68024d63dd13683d53e82259a5f23bea6d2ece4a Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Thu, 15 May 2025 01:00:31 -0400 Subject: [PATCH 7/8] fix: nonexistentID is existed somehow --- test/integration/datasets/DatasetsRepository.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index 108ad117..c5c93dcd 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -94,7 +94,7 @@ describe('DatasetsRepository', () => { const testCollectionAlias = 'datasetsRepositoryTestCollection' const sut: DatasetsRepository = new DatasetsRepository() - const nonExistentTestDatasetId = 100 + const nonExistentTestDatasetId = 1000 const filesRepositorySut = new FilesRepository() const directUploadSut: DirectUploadClient = new DirectUploadClient(filesRepositorySut) From 13091da9b456406bff68879fbda7f89d96ab874e Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Thu, 15 May 2025 10:26:42 -0400 Subject: [PATCH 8/8] fix: naming of transformer --- src/files/infra/repositories/FilesRepository.ts | 4 ++-- .../transformers/fileVersionSummaryInfoTransformers.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/files/infra/repositories/FilesRepository.ts b/src/files/infra/repositories/FilesRepository.ts index 4ce66c54..f657a9a7 100644 --- a/src/files/infra/repositories/FilesRepository.ts +++ b/src/files/infra/repositories/FilesRepository.ts @@ -23,7 +23,7 @@ import { UpdateFileMetadataDTO } from '../../domain/dtos/UpdateFileMetadataDTO' import { ApiConstants } from '../../../core/infra/repositories/ApiConstants' import { RestrictFileDTO } from '../../domain/dtos/RestrictFileDTO' import { FileVersionSummaryInfo } from '../../domain/models/FileVersionSummaryInfo' -import { FileVersionSummaryInfoTransformers } from './transformers/fileVersionSummaryInfoTransformers' +import { transformFileVersionSummaryInfoResponseToFileVersionSummaryInfo } from './transformers/fileVersionSummaryInfoTransformers' export interface GetFilesQueryParams { includeDeaccessioned: boolean @@ -423,7 +423,7 @@ export class FilesRepository extends ApiRepository implements IFilesRepository { this.buildApiEndpoint(this.filesResourceName, 'versionDifferences', fileId), true ) - .then((response) => FileVersionSummaryInfoTransformers(response)) + .then((response) => transformFileVersionSummaryInfoResponseToFileVersionSummaryInfo(response)) .catch((error) => { throw error }) diff --git a/src/files/infra/repositories/transformers/fileVersionSummaryInfoTransformers.ts b/src/files/infra/repositories/transformers/fileVersionSummaryInfoTransformers.ts index 8f827b0f..46eb4049 100644 --- a/src/files/infra/repositories/transformers/fileVersionSummaryInfoTransformers.ts +++ b/src/files/infra/repositories/transformers/fileVersionSummaryInfoTransformers.ts @@ -27,7 +27,7 @@ export interface FileVersionSummaryInfoPayload { versionNote?: string } -export const FileVersionSummaryInfoTransformers = ( +export const transformFileVersionSummaryInfoResponseToFileVersionSummaryInfo = ( response: AxiosResponse ): FileVersionSummaryInfo[] => { const payload = response.data.data