Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions docs/useCases.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ The different use cases currently available in the package are classified below,
- [Get User Permissions on a File](#get-user-permissions-on-a-file)
- [List Files in a Dataset](#list-files-in-a-dataset)
- [Is File Deleted](#is-file-deleted)
- [Get File Version Summaries](#get-file-version-summaries)
- [Files write use cases](#files-write-use-cases)
- [File Uploading Use Cases](#file-uploading-use-cases)
- [Delete a File](#delete-a-file)
Expand Down Expand Up @@ -1596,6 +1597,28 @@ _See [use case](../src/files/domain/useCases/isFileDeleted.ts) implementation_.

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

#### Get File Version Summaries

Get the file versions summaries, return a list of summaries for each 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
Expand Down
27 changes: 27 additions & 0 deletions src/files/domain/models/FileVersionSummaryInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { DatasetVersionState } from '../../../datasets/domain/models/Dataset'

export interface FileVersionSummaryInfo {
datasetVersion: string
contributors?: string
publishedDate?: string
fileDifferenceSummary?: FileDifferenceSummary
versionState?: DatasetVersionState
datafileId: number
persistentId?: string
versionNote?: string
}

export type FileDifferenceSummary = {
file?: FileChangeType
fileAccess?: 'Restricted' | 'Unrestricted'
fileMetadata?: FileMetadataChange[]
deaccessionedReason?: string
fileTags?: { [key in FileChangeType]?: number }
}

export type FileChangeType = 'Added' | 'Deleted' | 'Replaced' | 'Changed'

export interface FileMetadataChange {
name: string
action: FileChangeType
}
3 changes: 3 additions & 0 deletions src/files/domain/repositories/IFilesRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -85,5 +86,7 @@ export interface IFilesRepository {
replace?: boolean
): Promise<void>

getFileVersionSummaries(fileId: number | string): Promise<FileVersionSummaryInfo[]>

isFileDeleted(fileId: number | string): Promise<boolean>
}
21 changes: 21 additions & 0 deletions src/files/domain/useCases/GetFileVersionSummaries.ts
Original file line number Diff line number Diff line change
@@ -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<FileVersionSummaryInfo[]> {
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<FileVersionSummaryInfo[]>} - An array of FileVersionSummaryInfo.
*/
async execute(fileId: number | string): Promise<FileVersionSummaryInfo[]> {
return await this.filesRepository.getFileVersionSummaries(fileId)
}
}
3 changes: 3 additions & 0 deletions src/files/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
import { IsFileDeleted } from './domain/useCases/IsFileDeleted'

const filesRepository = new FilesRepository()
Expand All @@ -39,6 +40,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)
const isFileDeleted = new IsFileDeleted(filesRepository)

export {
Expand All @@ -59,6 +61,7 @@ export {
updateFileTabularTags,
updateFileCategories,
replaceFile,
getFileVersionSummaries,
isFileDeleted
}

Expand Down
13 changes: 13 additions & 0 deletions src/files/infra/repositories/FilesRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ 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'
import { transformFileVersionSummaryInfoResponseToFileVersionSummaryInfo } from './transformers/fileVersionSummaryInfoTransformers'

export interface GetFilesQueryParams {
includeDeaccessioned: boolean
Expand Down Expand Up @@ -416,6 +418,17 @@ export class FilesRepository extends ApiRepository implements IFilesRepository {
})
}

public async getFileVersionSummaries(fileId: number | string): Promise<FileVersionSummaryInfo[]> {
return this.doGet(
this.buildApiEndpoint(this.filesResourceName, 'versionDifferences', fileId),
true
)
.then((response) => transformFileVersionSummaryInfoResponseToFileVersionSummaryInfo(response))
.catch((error) => {
throw error
})
}

public async isFileDeleted(fileId: number | string): Promise<boolean> {
return this.doGet(this.buildApiEndpoint(this.filesResourceName, 'hasBeenDeleted', fileId), true)
.then((response) => response.data.data)
Expand Down
Original file line number Diff line number Diff line change
@@ -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 transformFileVersionSummaryInfoResponseToFileVersionSummaryInfo = (
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
}
})
}
2 changes: 1 addition & 1 deletion test/integration/datasets/DatasetsRepository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
162 changes: 162 additions & 0 deletions test/integration/files/FilesRepository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ import {
} from '../../testHelpers/collections/collectionHelper'
import { RestrictFileDTO } from '../../../src/files/domain/dtos/RestrictFileDTO'
import { DatasetsRepository } from '../../../src/datasets/infra/repositories/DatasetsRepository'
import { FileVersionSummaryInfo } from '../../../src/files/domain/models/FileVersionSummaryInfo'
import { DatasetVersionState } from '../../../src/datasets'
import { DirectUploadClient } from '../../../src/files/infra/clients/DirectUploadClient'

describe('FilesRepository', () => {
Expand Down Expand Up @@ -764,6 +766,166 @@ 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',
versionState: DatasetVersionState.DRAFT,
contributors: 'Dataverse Admin',
datafileId: testFile.id,
persistentId: testFile.persistentId,
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',
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)

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',
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)
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',
versionState: DatasetVersionState.DRAFT,
contributors: 'Dataverse Admin',
datafileId: testFile.id,
persistentId: testFile.persistentId,
publishedDate: actual[0].publishedDate,
versionNote: undefined,
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'
Expand Down
Loading
Loading