Skip to content

Commit 5002c75

Browse files
authored
Merge pull request #299 from IQSS/297-file-page-file-version-summary-use-case
Feat: use case of getFileVersionSummaries
2 parents 6966a11 + 13091da commit 5002c75

File tree

10 files changed

+349
-1
lines changed

10 files changed

+349
-1
lines changed

docs/useCases.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ The different use cases currently available in the package are classified below,
5353
- [Get User Permissions on a File](#get-user-permissions-on-a-file)
5454
- [List Files in a Dataset](#list-files-in-a-dataset)
5555
- [Is File Deleted](#is-file-deleted)
56+
- [Get File Version Summaries](#get-file-version-summaries)
5657
- [Files write use cases](#files-write-use-cases)
5758
- [File Uploading Use Cases](#file-uploading-use-cases)
5859
- [Delete a File](#delete-a-file)
@@ -1596,6 +1597,28 @@ _See [use case](../src/files/domain/useCases/isFileDeleted.ts) implementation_.
15961597

15971598
The `fileId` parameter can be a string, for persistent identifiers, or a number, for numeric identifiers.
15981599

1600+
#### Get File Version Summaries
1601+
1602+
Get the file versions summaries, return a list of summaries for each version
1603+
1604+
##### Example call:
1605+
1606+
```typescript
1607+
import { getFileVersionSummaries } from '@iqss/dataverse-client-javascript'
1608+
1609+
/* ... */
1610+
1611+
const fileId = 1
1612+
1613+
getFileVersionSummaries.execute(fileId).then((fileVersionSummaries: fileVersionSummaryInfo[]) => {
1614+
/* ... */
1615+
})
1616+
1617+
/* ... */
1618+
```
1619+
1620+
_See [use case](../src/files/domain/useCases/GetFileVersionSummaries.ts) implementation_.
1621+
15991622
## Metadata Blocks
16001623

16011624
### Metadata Blocks read use cases
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { DatasetVersionState } from '../../../datasets/domain/models/Dataset'
2+
3+
export interface FileVersionSummaryInfo {
4+
datasetVersion: string
5+
contributors?: string
6+
publishedDate?: string
7+
fileDifferenceSummary?: FileDifferenceSummary
8+
versionState?: DatasetVersionState
9+
datafileId: number
10+
persistentId?: string
11+
versionNote?: string
12+
}
13+
14+
export type FileDifferenceSummary = {
15+
file?: FileChangeType
16+
fileAccess?: 'Restricted' | 'Unrestricted'
17+
fileMetadata?: FileMetadataChange[]
18+
deaccessionedReason?: string
19+
fileTags?: { [key in FileChangeType]?: number }
20+
}
21+
22+
export type FileChangeType = 'Added' | 'Deleted' | 'Replaced' | 'Changed'
23+
24+
export interface FileMetadataChange {
25+
name: string
26+
action: FileChangeType
27+
}

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(
@@ -85,5 +86,7 @@ export interface IFilesRepository {
8586
replace?: boolean
8687
): Promise<void>
8788

89+
getFileVersionSummaries(fileId: number | string): Promise<FileVersionSummaryInfo[]>
90+
8891
isFileDeleted(fileId: number | string): Promise<boolean>
8992
}
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: 3 additions & 0 deletions
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
import { IsFileDeleted } from './domain/useCases/IsFileDeleted'
2122

2223
const filesRepository = new FilesRepository()
@@ -39,6 +40,7 @@ const restrictFile = new RestrictFile(filesRepository)
3940
const updateFileMetadata = new UpdateFileMetadata(filesRepository)
4041
const updateFileTabularTags = new UpdateFileTabularTags(filesRepository)
4142
const updateFileCategories = new UpdateFileCategories(filesRepository)
43+
const getFileVersionSummaries = new GetFileVersionSummaries(filesRepository)
4244
const isFileDeleted = new IsFileDeleted(filesRepository)
4345

4446
export {
@@ -59,6 +61,7 @@ export {
5961
updateFileTabularTags,
6062
updateFileCategories,
6163
replaceFile,
64+
getFileVersionSummaries,
6265
isFileDeleted
6366
}
6467

src/files/infra/repositories/FilesRepository.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ 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'
26+
import { transformFileVersionSummaryInfoResponseToFileVersionSummaryInfo } from './transformers/fileVersionSummaryInfoTransformers'
2527

2628
export interface GetFilesQueryParams {
2729
includeDeaccessioned: boolean
@@ -416,6 +418,17 @@ export class FilesRepository extends ApiRepository implements IFilesRepository {
416418
})
417419
}
418420

421+
public async getFileVersionSummaries(fileId: number | string): Promise<FileVersionSummaryInfo[]> {
422+
return this.doGet(
423+
this.buildApiEndpoint(this.filesResourceName, 'versionDifferences', fileId),
424+
true
425+
)
426+
.then((response) => transformFileVersionSummaryInfoResponseToFileVersionSummaryInfo(response))
427+
.catch((error) => {
428+
throw error
429+
})
430+
}
431+
419432
public async isFileDeleted(fileId: number | string): Promise<boolean> {
420433
return this.doGet(this.buildApiEndpoint(this.filesResourceName, 'hasBeenDeleted', fileId), true)
421434
.then((response) => response.data.data)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { AxiosResponse } from 'axios'
2+
import {
3+
FileVersionSummaryInfo,
4+
FileMetadataChange,
5+
FileDifferenceSummary
6+
} from '../../../domain/models/FileVersionSummaryInfo'
7+
import { DatasetVersionState } from '../../../../datasets/domain/models/Dataset'
8+
9+
export interface FileVersionSummaryInfoPayload {
10+
datasetVersion: string
11+
contributors?: string
12+
publishedDate?: string
13+
fileDifferenceSummary?: {
14+
file?: string
15+
FileAccess?: string
16+
FileMetadata?: FileMetadataChange[]
17+
deaccessionedReason?: string
18+
FileTags?: {
19+
Added?: number
20+
Deleted?: number
21+
Changed?: number
22+
}
23+
}
24+
versionState?: DatasetVersionState
25+
datafileId: number
26+
persistentId?: string
27+
versionNote?: string
28+
}
29+
30+
export const transformFileVersionSummaryInfoResponseToFileVersionSummaryInfo = (
31+
response: AxiosResponse
32+
): FileVersionSummaryInfo[] => {
33+
const payload = response.data.data
34+
35+
return payload.map((item: FileVersionSummaryInfoPayload): FileVersionSummaryInfo => {
36+
const summary = item.fileDifferenceSummary || {}
37+
38+
const fileDifferenceSummary: FileDifferenceSummary = {
39+
...(summary.file && { file: summary.file }),
40+
...(summary.FileAccess && { fileAccess: summary.FileAccess }),
41+
...(summary.FileMetadata && { fileMetadata: summary.FileMetadata }),
42+
...(summary.deaccessionedReason && { deaccessionedReason: summary.deaccessionedReason }),
43+
...(summary.FileTags && { fileTags: summary.FileTags })
44+
} as FileDifferenceSummary
45+
46+
return {
47+
datasetVersion: item.datasetVersion,
48+
contributors: item.contributors,
49+
publishedDate: item.publishedDate,
50+
fileDifferenceSummary: fileDifferenceSummary,
51+
versionState: item.versionState,
52+
datafileId: item.datafileId,
53+
persistentId: item.persistentId,
54+
versionNote: item.versionNote
55+
}
56+
})
57+
}

test/integration/datasets/DatasetsRepository.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ describe('DatasetsRepository', () => {
9494
const testCollectionAlias = 'datasetsRepositoryTestCollection'
9595

9696
const sut: DatasetsRepository = new DatasetsRepository()
97-
const nonExistentTestDatasetId = 100
97+
const nonExistentTestDatasetId = 1000
9898

9999
const filesRepositorySut = new FilesRepository()
100100
const directUploadSut: DirectUploadClient = new DirectUploadClient(filesRepositorySut)

test/integration/files/FilesRepository.test.ts

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ import {
4545
} from '../../testHelpers/collections/collectionHelper'
4646
import { RestrictFileDTO } from '../../../src/files/domain/dtos/RestrictFileDTO'
4747
import { DatasetsRepository } from '../../../src/datasets/infra/repositories/DatasetsRepository'
48+
import { FileVersionSummaryInfo } from '../../../src/files/domain/models/FileVersionSummaryInfo'
49+
import { DatasetVersionState } from '../../../src/datasets'
4850
import { DirectUploadClient } from '../../../src/files/infra/clients/DirectUploadClient'
4951

5052
describe('FilesRepository', () => {
@@ -764,6 +766,166 @@ describe('FilesRepository', () => {
764766
})
765767
})
766768

769+
describe('getFileVersionSummaries', () => {
770+
let fileTestDatasetIds: CreatedDatasetIdentifiers
771+
const testTextFile1Name = 'test-file-1.txt'
772+
773+
beforeEach(async () => {
774+
try {
775+
fileTestDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO)
776+
} catch (error) {
777+
throw new Error('Tests beforeEach(): Error while creating test dataset')
778+
}
779+
await uploadFileViaApi(fileTestDatasetIds.numericId, testTextFile1Name).catch(() => {
780+
throw new Error(`Tests beforeEach(): Error while uploading file ${testTextFile1Name}`)
781+
})
782+
})
783+
784+
test('should return file version summaries when file draft exists', async () => {
785+
const currentTestFilesSubset = await sut.getDatasetFiles(
786+
fileTestDatasetIds.numericId,
787+
latestDatasetVersionId,
788+
false,
789+
FileOrderCriteria.NAME_AZ
790+
)
791+
const testFile = currentTestFilesSubset.files[0]
792+
const actual = await sut.getFileVersionSummaries(testFile.id)
793+
794+
const fileSummmaries: FileVersionSummaryInfo = {
795+
datasetVersion: 'DRAFT',
796+
versionState: DatasetVersionState.DRAFT,
797+
contributors: 'Dataverse Admin',
798+
datafileId: testFile.id,
799+
persistentId: testFile.persistentId,
800+
fileDifferenceSummary: { file: 'Added' }
801+
}
802+
803+
expect(actual).toHaveLength(1)
804+
expect(actual[0]).toEqual(fileSummmaries)
805+
deleteUnpublishedDatasetViaApi(fileTestDatasetIds.numericId)
806+
})
807+
808+
test('should return file version summaries when dataset is deaccessioned', async () => {
809+
await publishDatasetViaApi(fileTestDatasetIds.numericId).catch(() => {
810+
throw new Error('Error while publishing test Dataset')
811+
})
812+
813+
await waitForNoLocks(fileTestDatasetIds.numericId, 10).catch(() => {
814+
throw new Error('Error while waiting for no locks')
815+
})
816+
817+
const datasetFiles = await sut.getDatasetFiles(
818+
fileTestDatasetIds.numericId,
819+
latestDatasetVersionId,
820+
false,
821+
FileOrderCriteria.NAME_AZ
822+
)
823+
const testFile = datasetFiles.files[0]
824+
const publishedFileVersionSummariesActual = await sut.getFileVersionSummaries(testFile.id)
825+
826+
const publishedFileVersionSummmaries: FileVersionSummaryInfo = {
827+
datasetVersion: '1.0',
828+
publishedDate: publishedFileVersionSummariesActual[0].publishedDate,
829+
versionState: DatasetVersionState.RELEASED,
830+
contributors: 'Dataverse Admin',
831+
datafileId: testFile.id,
832+
persistentId: testFile.persistentId,
833+
fileDifferenceSummary: { file: 'Added' }
834+
}
835+
836+
expect(publishedFileVersionSummariesActual).toHaveLength(1)
837+
expect(publishedFileVersionSummariesActual[0]).toEqual(publishedFileVersionSummmaries)
838+
839+
await deaccessionDatasetViaApi(fileTestDatasetIds.numericId, '1.0').catch(() => {
840+
throw new Error('Error while deaccessioning test Dataset')
841+
})
842+
843+
const actual = await sut.getFileVersionSummaries(testFile.id)
844+
845+
const fileSummmaries: FileVersionSummaryInfo = {
846+
datasetVersion: '1.0',
847+
publishedDate: publishedFileVersionSummariesActual[0].publishedDate,
848+
versionState: DatasetVersionState.DEACCESSIONED,
849+
contributors: 'Dataverse Admin',
850+
datafileId: testFile.id,
851+
persistentId: testFile.persistentId,
852+
fileDifferenceSummary: {
853+
deaccessionedReason: 'Test reason.',
854+
file: 'Added'
855+
}
856+
}
857+
858+
expect(actual).toHaveLength(1)
859+
expect(actual[0]).toEqual(fileSummmaries)
860+
deletePublishedDatasetViaApi(fileTestDatasetIds.persistentId)
861+
})
862+
863+
test('should return file version summaries when file is updated', async () => {
864+
await publishDatasetViaApi(fileTestDatasetIds.numericId).catch(() => {
865+
throw new Error('Error while publishing test Dataset')
866+
})
867+
868+
await waitForNoLocks(fileTestDatasetIds.numericId, 10).catch(() => {
869+
throw new Error('Error while waiting for no locks')
870+
})
871+
872+
const datasetFiles = await sut.getDatasetFiles(
873+
fileTestDatasetIds.numericId,
874+
latestDatasetVersionId,
875+
false,
876+
FileOrderCriteria.NAME_AZ
877+
)
878+
const testFile = datasetFiles.files[0]
879+
const actual = await sut.getFileVersionSummaries(testFile.id)
880+
881+
expect(actual).toHaveLength(1)
882+
883+
await sut.updateFileMetadata(testFile.id, {
884+
description: 'My description test.',
885+
categories: ['Data', 'Test'],
886+
label: 'myfile.txt',
887+
directoryLabel: 'mydir',
888+
restrict: true
889+
})
890+
const updatedFileVersionSummariesActual = await sut.getFileVersionSummaries(testFile.id)
891+
const updatedFileVersionSummaries: FileVersionSummaryInfo = {
892+
datasetVersion: 'DRAFT',
893+
versionState: DatasetVersionState.DRAFT,
894+
contributors: 'Dataverse Admin',
895+
datafileId: testFile.id,
896+
persistentId: testFile.persistentId,
897+
publishedDate: actual[0].publishedDate,
898+
versionNote: undefined,
899+
fileDifferenceSummary: {
900+
fileMetadata: [
901+
{
902+
name: 'File Name',
903+
action: 'Changed'
904+
},
905+
{
906+
name: 'Description',
907+
action: 'Changed'
908+
}
909+
],
910+
fileTags: {
911+
Added: 2
912+
},
913+
fileAccess: 'Restricted'
914+
}
915+
}
916+
917+
expect(updatedFileVersionSummariesActual).toHaveLength(2)
918+
expect(updatedFileVersionSummariesActual[0]).toEqual(updatedFileVersionSummaries)
919+
deletePublishedDatasetViaApi(fileTestDatasetIds.persistentId)
920+
})
921+
922+
test('should return error when file does not exist', async () => {
923+
const expectedError = new ReadError(`[404] File with ID ${nonExistentFiledId} not found.`)
924+
925+
await expect(sut.getFileVersionSummaries(nonExistentFiledId)).rejects.toThrow(expectedError)
926+
})
927+
})
928+
767929
describe('deleteFile', () => {
768930
let deleFileTestDatasetIds: CreatedDatasetIdentifiers
769931
const testTextFile1Name = 'test-file-1.txt'

0 commit comments

Comments
 (0)