Skip to content

Commit 4850ee4

Browse files
committed
feat: use case of getFileVersionSummaries
1 parent c748a8f commit 4850ee4

File tree

8 files changed

+329
-1
lines changed

8 files changed

+329
-1
lines changed

docs/useCases.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1571,6 +1571,28 @@ If restrict is false then enableAccessRequest and termsOfAccess are ignored
15711571
If restrict is true and enableAccessRequest is false then termsOfAccess is required.
15721572
The enableAccessRequest and termsOfAccess are applied to the Draft version of the Dataset and affect all of the restricted files in said Draft version.
15731573

1574+
#### Get File Version Summaries
1575+
1576+
Returns an array of [FileVersionSummaryInfo](../src/files/domain/models/FileVersionSummaryInfo.ts) that contains information about what changed in every specific version.
1577+
1578+
##### Example call:
1579+
1580+
```typescript
1581+
import { getFileVersionSummaries } from '@iqss/dataverse-client-javascript'
1582+
1583+
/* ... */
1584+
1585+
const fileId = 1
1586+
1587+
getFileVersionSummaries.execute(fileId).then((fileVersionSummaries: fileVersionSummaryInfo[]) => {
1588+
/* ... */
1589+
})
1590+
1591+
/* ... */
1592+
```
1593+
1594+
_See [use case](../src/files/domain/useCases/GetFileVersionSummaries.ts) implementation_.
1595+
15741596
## Metadata Blocks
15751597

15761598
### Metadata Blocks read use cases
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
export interface FileVersionSummaryInfo {
2+
datasetVersion: string
3+
versionNumber?: number
4+
versionMinorNumber?: number
5+
contributors?: string
6+
publishedDate: string
7+
fileDifferenceSummary?: FileDifferenceSummary
8+
isDraft: boolean
9+
isDeaccessioned: boolean
10+
isReleased: boolean
11+
versionState?: FileVersionState
12+
datafileId: number
13+
persistentId?: string
14+
versionNote?: string
15+
}
16+
17+
export enum FileVersionState {
18+
RELEASED = 'RELEASED',
19+
DEACCESSIONED = 'DEACCESSIONED',
20+
DRAFT = 'DRAFT'
21+
}
22+
23+
export type FileDifferenceSummary = {
24+
file?: FileChangeType
25+
FileAccess?: FileAccessChangeType
26+
FileMetadata?: FileMetadataChange[]
27+
deaccessionedReason?: string
28+
FileTags?: FileTagChange
29+
}
30+
31+
export type FileChangeType = 'Added' | 'Deleted' | 'Replaced' | 'Changed'
32+
export type FileAccessChangeType = 'Restricted' | 'Public'
33+
34+
export type FileTagChange = {
35+
Added?: number
36+
Deleted?: number
37+
Changed?: number
38+
}
39+
40+
export interface FileMetadataChange {
41+
name: string
42+
action: FileChangeType
43+
}

src/files/domain/repositories/IFilesRepository.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { FileUploadDestination } from '../models/FileUploadDestination'
1010
import { UploadedFileDTO } from '../dtos/UploadedFileDTO'
1111
import { UpdateFileMetadataDTO } from '../dtos/UpdateFileMetadataDTO'
1212
import { RestrictFileDTO } from '../dtos/RestrictFileDTO'
13+
import { FileVersionSummaryInfo } from '../models/FileVersionSummaryInfo'
1314

1415
export interface IFilesRepository {
1516
getDatasetFiles(
@@ -84,4 +85,6 @@ export interface IFilesRepository {
8485
categories: string[],
8586
replace?: boolean
8687
): Promise<void>
88+
89+
getFileVersionSummaries(fileId: number | string): Promise<FileVersionSummaryInfo[]>
8790
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { UseCase } from '../../../core/domain/useCases/UseCase'
2+
import { FileVersionSummaryInfo } from '../models/FileVersionSummaryInfo'
3+
import { IFilesRepository } from '../repositories/IFilesRepository'
4+
5+
export class GetFileVersionSummaries implements UseCase<FileVersionSummaryInfo[]> {
6+
private filesRepository: IFilesRepository
7+
8+
constructor(filesRepository: IFilesRepository) {
9+
this.filesRepository = filesRepository
10+
}
11+
12+
/**
13+
* Returns a list of versions for a given file including a summary of differences between consecutive versions
14+
*
15+
* @param {number | string} [fileId] - The file identifier, which can be a string (for persistent identifiers), or a number (for numeric identifiers).
16+
* @returns {Promise<FileVersionSummaryInfo[]>} - An array of FileVersionSummaryInfo.
17+
*/
18+
async execute(fileId: number | string): Promise<FileVersionSummaryInfo[]> {
19+
return await this.filesRepository.getFileVersionSummaries(fileId)
20+
}
21+
}

src/files/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { RestrictFile } from './domain/useCases/RestrictFile'
1717
import { UpdateFileMetadata } from './domain/useCases/UpdateFileMetadata'
1818
import { UpdateFileTabularTags } from './domain/useCases/UpdateFileTabularTags'
1919
import { UpdateFileCategories } from './domain/useCases/UpdateFileCategories'
20+
import { GetFileVersionSummaries } from './domain/useCases/GetFileVersionSummaries'
2021

2122
const filesRepository = new FilesRepository()
2223
const directUploadClient = new DirectUploadClient(filesRepository)
@@ -38,6 +39,7 @@ const restrictFile = new RestrictFile(filesRepository)
3839
const updateFileMetadata = new UpdateFileMetadata(filesRepository)
3940
const updateFileTabularTags = new UpdateFileTabularTags(filesRepository)
4041
const updateFileCategories = new UpdateFileCategories(filesRepository)
42+
const getFileVersionSummaries = new GetFileVersionSummaries(filesRepository)
4143

4244
export {
4345
getDatasetFiles,
@@ -56,7 +58,8 @@ export {
5658
updateFileMetadata,
5759
updateFileTabularTags,
5860
updateFileCategories,
59-
replaceFile
61+
replaceFile,
62+
getFileVersionSummaries
6063
}
6164

6265
export { FileModel as File, FileEmbargo, FileChecksum } from './domain/models/FileModel'

src/files/infra/repositories/FilesRepository.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { UploadedFileDTO } from '../../domain/dtos/UploadedFileDTO'
2222
import { UpdateFileMetadataDTO } from '../../domain/dtos/UpdateFileMetadataDTO'
2323
import { ApiConstants } from '../../../core/infra/repositories/ApiConstants'
2424
import { RestrictFileDTO } from '../../domain/dtos/RestrictFileDTO'
25+
import { FileVersionSummaryInfo } from '../../domain/models/FileVersionSummaryInfo'
2526

2627
export interface GetFilesQueryParams {
2728
includeDeaccessioned: boolean
@@ -415,4 +416,15 @@ export class FilesRepository extends ApiRepository implements IFilesRepository {
415416
throw error
416417
})
417418
}
419+
420+
public async getFileVersionSummaries(fileId: number | string): Promise<FileVersionSummaryInfo[]> {
421+
return this.doGet(
422+
this.buildApiEndpoint(this.filesResourceName, 'versionDifferences', fileId),
423+
true
424+
)
425+
.then((response) => response.data.data)
426+
.catch((error) => {
427+
throw error
428+
})
429+
}
418430
}

test/integration/files/FilesRepository.test.ts

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ import {
4343
} from '../../testHelpers/collections/collectionHelper'
4444
import { RestrictFileDTO } from '../../../src/files/domain/dtos/RestrictFileDTO'
4545
import { DatasetsRepository } from '../../../src/datasets/infra/repositories/DatasetsRepository'
46+
import {
47+
FileVersionState,
48+
FileVersionSummaryInfo
49+
} from '../../../src/files/domain/models/FileVersionSummaryInfo'
4650

4751
describe('FilesRepository', () => {
4852
const sut: FilesRepository = new FilesRepository()
@@ -761,6 +765,182 @@ describe('FilesRepository', () => {
761765
})
762766
})
763767

768+
describe('getFileVersionSummaries', () => {
769+
let fileTestDatasetIds: CreatedDatasetIdentifiers
770+
const testTextFile1Name = 'test-file-1.txt'
771+
772+
beforeEach(async () => {
773+
try {
774+
fileTestDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO)
775+
} catch (error) {
776+
throw new Error('Tests beforeEach(): Error while creating test dataset')
777+
}
778+
await uploadFileViaApi(fileTestDatasetIds.numericId, testTextFile1Name).catch(() => {
779+
throw new Error(`Tests beforeEach(): Error while uploading file ${testTextFile1Name}`)
780+
})
781+
})
782+
783+
test('should return file version summaries when file draft exists', async () => {
784+
const currentTestFilesSubset = await sut.getDatasetFiles(
785+
fileTestDatasetIds.numericId,
786+
latestDatasetVersionId,
787+
false,
788+
FileOrderCriteria.NAME_AZ
789+
)
790+
const testFile = currentTestFilesSubset.files[0]
791+
const actual = await sut.getFileVersionSummaries(testFile.id)
792+
793+
const fileSummmaries: FileVersionSummaryInfo = {
794+
datasetVersion: 'DRAFT',
795+
isDraft: true,
796+
isReleased: false,
797+
isDeaccessioned: false,
798+
versionState: FileVersionState.DRAFT,
799+
contributors: 'Dataverse Admin',
800+
datafileId: testFile.id,
801+
persistentId: testFile.persistentId,
802+
publishedDate: '',
803+
fileDifferenceSummary: { file: 'Added' }
804+
}
805+
806+
expect(actual).toHaveLength(1)
807+
expect(actual[0]).toEqual(fileSummmaries)
808+
deleteUnpublishedDatasetViaApi(fileTestDatasetIds.numericId)
809+
})
810+
811+
test('should return file version summaries when dataset is deaccessioned', async () => {
812+
await publishDatasetViaApi(fileTestDatasetIds.numericId).catch(() => {
813+
throw new Error('Error while publishing test Dataset')
814+
})
815+
816+
await waitForNoLocks(fileTestDatasetIds.numericId, 10).catch(() => {
817+
throw new Error('Error while waiting for no locks')
818+
})
819+
820+
const datasetFiles = await sut.getDatasetFiles(
821+
fileTestDatasetIds.numericId,
822+
latestDatasetVersionId,
823+
false,
824+
FileOrderCriteria.NAME_AZ
825+
)
826+
const testFile = datasetFiles.files[0]
827+
const publishedFileVersionSummariesActual = await sut.getFileVersionSummaries(testFile.id)
828+
829+
const publishedFileVersionSummmaries: FileVersionSummaryInfo = {
830+
datasetVersion: '1.0',
831+
isDraft: false,
832+
isReleased: true,
833+
isDeaccessioned: false,
834+
versionNumber: 1,
835+
versionMinorNumber: 0,
836+
publishedDate: new Date().toISOString().split('T')[0], // Format: yyyy-mm-dd
837+
versionState: FileVersionState.RELEASED,
838+
contributors: 'Dataverse Admin',
839+
datafileId: testFile.id,
840+
persistentId: testFile.persistentId,
841+
fileDifferenceSummary: { file: 'Added' }
842+
}
843+
844+
expect(publishedFileVersionSummariesActual).toHaveLength(1)
845+
expect(publishedFileVersionSummariesActual[0]).toEqual(publishedFileVersionSummmaries)
846+
847+
await deaccessionDatasetViaApi(fileTestDatasetIds.numericId, '1.0').catch(() => {
848+
throw new Error('Error while deaccessioning test Dataset')
849+
})
850+
851+
const actual = await sut.getFileVersionSummaries(testFile.id)
852+
853+
const fileSummmaries: FileVersionSummaryInfo = {
854+
datasetVersion: '1.0',
855+
versionNumber: 1,
856+
versionMinorNumber: 0,
857+
publishedDate: new Date().toISOString().split('T')[0],
858+
isDraft: false,
859+
isReleased: false,
860+
isDeaccessioned: true,
861+
versionState: FileVersionState.DEACCESSIONED,
862+
contributors: 'Dataverse Admin',
863+
datafileId: testFile.id,
864+
persistentId: testFile.persistentId,
865+
fileDifferenceSummary: {
866+
deaccessionedReason: 'Test reason.',
867+
file: 'Added'
868+
}
869+
}
870+
871+
expect(actual).toHaveLength(1)
872+
expect(actual[0]).toEqual(fileSummmaries)
873+
deletePublishedDatasetViaApi(fileTestDatasetIds.persistentId)
874+
})
875+
876+
test('should return file version summaries when file is updated', async () => {
877+
await publishDatasetViaApi(fileTestDatasetIds.numericId).catch(() => {
878+
throw new Error('Error while publishing test Dataset')
879+
})
880+
881+
await waitForNoLocks(fileTestDatasetIds.numericId, 10).catch(() => {
882+
throw new Error('Error while waiting for no locks')
883+
})
884+
885+
const datasetFiles = await sut.getDatasetFiles(
886+
fileTestDatasetIds.numericId,
887+
latestDatasetVersionId,
888+
false,
889+
FileOrderCriteria.NAME_AZ
890+
)
891+
const testFile = datasetFiles.files[0]
892+
const actual = await sut.getFileVersionSummaries(testFile.id)
893+
894+
expect(actual).toHaveLength(1)
895+
896+
await sut.updateFileMetadata(testFile.id, {
897+
description: 'My description test.',
898+
categories: ['Data', 'Test'],
899+
label: 'myfile.txt',
900+
directoryLabel: 'mydir',
901+
restrict: true
902+
})
903+
const updatedFileVersionSummariesActual = await sut.getFileVersionSummaries(testFile.id)
904+
const updatedFileVersionSummaries: FileVersionSummaryInfo = {
905+
datasetVersion: 'DRAFT',
906+
publishedDate: '',
907+
isDraft: true,
908+
isReleased: false,
909+
isDeaccessioned: false,
910+
versionState: FileVersionState.DRAFT,
911+
contributors: 'Dataverse Admin',
912+
datafileId: testFile.id,
913+
persistentId: testFile.persistentId,
914+
fileDifferenceSummary: {
915+
FileMetadata: [
916+
{
917+
name: 'File Name',
918+
action: 'Changed'
919+
},
920+
{
921+
name: 'Description',
922+
action: 'Changed'
923+
}
924+
],
925+
FileTags: {
926+
Added: 2
927+
},
928+
FileAccess: 'Restricted'
929+
}
930+
}
931+
932+
expect(updatedFileVersionSummariesActual).toHaveLength(2)
933+
expect(updatedFileVersionSummariesActual[0]).toEqual(updatedFileVersionSummaries)
934+
deletePublishedDatasetViaApi(fileTestDatasetIds.persistentId)
935+
})
936+
937+
test('should return error when file does not exist', async () => {
938+
const expectedError = new ReadError(`[404] File with ID ${nonExistentFiledId} not found.`)
939+
940+
await expect(sut.getFileVersionSummaries(nonExistentFiledId)).rejects.toThrow(expectedError)
941+
})
942+
})
943+
764944
describe('deleteFile', () => {
765945
let deleFileTestDatasetIds: CreatedDatasetIdentifiers
766946
const testTextFile1Name = 'test-file-1.txt'
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { IFilesRepository } from '../../../src/files/domain/repositories/IFilesRepository'
2+
import { ReadError } from '../../../src'
3+
import { GetFileVersionSummaries } from '../../../src/files/domain/useCases/GetFileVersionSummaries'
4+
import { FileVersionSummaryInfo } from '../../../src/files/domain/models/FileVersionSummaryInfo'
5+
6+
describe('execute', () => {
7+
test('should return file on repository success when passing numeric id', async () => {
8+
const filesRepositoryStub: IFilesRepository = {} as IFilesRepository
9+
const fileVersionSummaries: FileVersionSummaryInfo[] = [
10+
{
11+
datasetVersion: '1.0',
12+
versionNumber: 1,
13+
versionMinorNumber: 0,
14+
contributors: 'John Doe',
15+
publishedDate: '2023-01-01',
16+
fileDifferenceSummary: {
17+
fileMetadata: [
18+
{
19+
name: 'file.txt',
20+
action: 'Added'
21+
}
22+
]
23+
},
24+
isDraft: false,
25+
isDeaccessioned: false,
26+
isReleased: false,
27+
datafileId: 1
28+
}
29+
]
30+
filesRepositoryStub.getFileVersionSummaries = jest.fn().mockResolvedValue(fileVersionSummaries)
31+
32+
const sut = new GetFileVersionSummaries(filesRepositoryStub)
33+
const actualFileVersionSummaries = await sut.execute(1)
34+
expect(actualFileVersionSummaries).toEqual(fileVersionSummaries)
35+
})
36+
37+
test('should return error result on repository error', async () => {
38+
const filesRepositoryStub: IFilesRepository = {} as IFilesRepository
39+
filesRepositoryStub.getFileVersionSummaries = jest.fn().mockRejectedValue(new ReadError())
40+
const sut = new GetFileVersionSummaries(filesRepositoryStub)
41+
42+
await expect(sut.execute(1)).rejects.toThrow(ReadError)
43+
})
44+
})

0 commit comments

Comments
 (0)