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
51 changes: 49 additions & 2 deletions docs/useCases.md
Original file line number Diff line number Diff line change
Expand Up @@ -1336,15 +1336,62 @@ const updateFileMetadataDTO = {
restrict: false
}

await updateFileMetadata.execute(fileId, updateFileMetadataDTO).then((fileId) => {
console.log(`File updated successfully with file ID: ${fileId}`)
await updateFileMetadata.execute(fileId, updateFileMetadataDTO).then(() => {
console.log(`File updated successfully`)
})
```

_See [use case](../src/files/domain/useCases/UpdateFileMetadata.ts) implementation_.

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

#### Update File Categories

Updates Categories of a File.

###### Example call:

```typescript
import { updateFileCategories } from '@iqss/dataverse-client-javascript'

/* ... */

const fileId: number | string = 123
const categories = ['category 1', 'category 1']
const replace = true

await updateFileCategories.execute(fileId, categories, replace).then(() => {
console.log(`File updated successfully`)
})
```

_See [use case](../src/files/domain/useCases/updateFileCategories.ts) implementation_.

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

#### Update File Tabular Tags

Updates Tabular Tags of a File.

###### Example call:

```typescript
import { updateFileTabularTags } from '@iqss/dataverse-client-javascript'

/* ... */

const fileId: number | string = 123
const tabularTags = ['Surveys']

await updateFileTabularTags.execute(fileId, tabularTags, replace).then(() => {
console.log(`File updated successfully`)
})
```

_See [use case](../src/files/domain/useCases/updateFileTabularTags.ts) implementation_.

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

#### Delete a File

Deletes a File.
Expand Down
12 changes: 12 additions & 0 deletions src/files/domain/repositories/IFilesRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,16 @@ export interface IFilesRepository {
fileId: number | string,
updateFileMetadataDTO: UpdateFileMetadataDTO
): Promise<void>

updateFileTabularTags(
fileId: number | string,
tabularTags: string[],
replace?: boolean
): Promise<void>

updateFileCategories(
fileId: number | string,
categories: string[],
replace?: boolean
): Promise<void>
}
23 changes: 23 additions & 0 deletions src/files/domain/useCases/UpdateFileCategories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { UseCase } from '../../../core/domain/useCases/UseCase'
import { IFilesRepository } from '../repositories/IFilesRepository'

export class UpdateFileCategories implements UseCase<void> {
private filesRepository: IFilesRepository

constructor(filesRepository: IFilesRepository) {
this.filesRepository = filesRepository
}

/**
* Updates the categories for a particular File.
* More detailed information about updating a file's categories behavior can be found in https://guides.dataverse.org/en/latest/api/native-api.html#updating-file-metadata
*
* @param {number | string} [fileId] - The file identifier, which can be a string (for persistent identifiers), or a number (for numeric identifiers).
* @param {string[]} [categories] - The categories to be added to the file.
* @param {boolean} [replace](optional) - If true, replaces the existing categories with the new ones. If false, adds the new categories to the existing ones.
* @returns {Promise<void>}
*/
async execute(fileId: number | string, categories: string[], replace?: boolean): Promise<void> {
await this.filesRepository.updateFileCategories(fileId, categories, replace)
}
}
23 changes: 23 additions & 0 deletions src/files/domain/useCases/UpdateFileTabularTags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { UseCase } from '../../../core/domain/useCases/UseCase'
import { IFilesRepository } from '../repositories/IFilesRepository'

export class UpdateFileTabularTags implements UseCase<void> {
private filesRepository: IFilesRepository

constructor(filesRepository: IFilesRepository) {
this.filesRepository = filesRepository
}

/**
* Updates the tabular tabular Tags for a particular File.
* More detailed information about updating a file's tabularTags behavior can be found in https://guides.dataverse.org/en/latest/api/native-api.html#updating-file-metadata
*
* @param {number | string} [fileId] - The file identifier, which can be a string (for persistent identifiers), or a number (for numeric identifiers).
* @param {string[]} [tabularTags] - The tabular tags to be added to the file.
* @param {boolean} [replace](optional) - If true, replaces the existing tabularTags with the new ones. If false, adds the new tabularTags to the existing ones.
* @returns {Promise<void>}
*/
async execute(fileId: number | string, tabularTags: string[], replace?: boolean): Promise<void> {
await this.filesRepository.updateFileTabularTags(fileId, tabularTags, replace)
}
}
6 changes: 6 additions & 0 deletions src/files/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { DeleteFile } from './domain/useCases/DeleteFile'
import { ReplaceFile } from './domain/useCases/ReplaceFile'
import { RestrictFile } from './domain/useCases/RestrictFile'
import { UpdateFileMetadata } from './domain/useCases/UpdateFileMetadata'
import { UpdateFileTabularTags } from './domain/useCases/UpdateFileTabularTags'
import { UpdateFileCategories } from './domain/useCases/UpdateFileCategories'

const filesRepository = new FilesRepository()
const directUploadClient = new DirectUploadClient(filesRepository)
Expand All @@ -34,6 +36,8 @@ const deleteFile = new DeleteFile(filesRepository)
const replaceFile = new ReplaceFile(filesRepository)
const restrictFile = new RestrictFile(filesRepository)
const updateFileMetadata = new UpdateFileMetadata(filesRepository)
const updateFileTabularTags = new UpdateFileTabularTags(filesRepository)
const updateFileCategories = new UpdateFileCategories(filesRepository)

export {
getDatasetFiles,
Expand All @@ -50,6 +54,8 @@ export {
deleteFile,
restrictFile,
updateFileMetadata,
updateFileTabularTags,
updateFileCategories,
replaceFile
}

Expand Down
34 changes: 34 additions & 0 deletions src/files/infra/repositories/FilesRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,4 +381,38 @@ export class FilesRepository extends ApiRepository implements IFilesRepository {
throw error
})
}

public async updateFileTabularTags(
fileId: number | string,
tabularTags: string[],
replace?: boolean
): Promise<void> {
const queryParams = replace !== undefined ? { replace } : {}
return this.doPost(
this.buildApiEndpoint(this.filesResourceName, 'metadata/tabularTags', fileId),
{ tabularTags },
queryParams
)
.then(() => undefined)
.catch((error) => {
throw error
})
}

public async updateFileCategories(
fileId: number | string,
categories: string[],
replace?: boolean
): Promise<void> {
const queryParams = replace !== undefined ? { replace } : {}
return this.doPost(
this.buildApiEndpoint(this.filesResourceName, 'metadata/categories', fileId),
{ categories },
queryParams
)
.then(() => undefined)
.catch((error) => {
throw error
})
}
}
146 changes: 146 additions & 0 deletions test/functional/files/UpdateFileCategories.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import {
ApiConfig,
createDataset,
CreatedDatasetIdentifiers,
WriteError,
updateFileCategories,
getFile,
DatasetNotNumberedVersion,
getDatasetFiles
} from '../../../src'
import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'
import {
createCollectionViaApi,
deleteCollectionViaApi
} from '../../testHelpers/collections/collectionHelper'
import { deleteUnpublishedDatasetViaApi } from '../../testHelpers/datasets/datasetHelper'
import { uploadFileViaApi } from '../../testHelpers/files/filesHelper'
import { TestConstants } from '../../testHelpers/TestConstants'
import { FileModel } from '../../../src/files/domain/models/FileModel'

describe('execute', () => {
const testCollectionAlias = 'updateFileMetadataFunctionalTest-categories'
let testDatasetIds: CreatedDatasetIdentifiers
const testTextFile1Name = 'test-file-1.txt'
const metadataUpdate = ['file']

beforeAll(async () => {
ApiConfig.init(
TestConstants.TEST_API_URL,
DataverseApiAuthMechanism.API_KEY,
process.env.TEST_API_KEY
)
await createCollectionViaApi(testCollectionAlias)

try {
testDatasetIds = await createDataset.execute(
TestConstants.TEST_NEW_DATASET_DTO,
testCollectionAlias
)
} catch (error) {
throw new Error('Tests beforeAll(): Error while creating test dataset')
}

await uploadFileViaApi(testDatasetIds.numericId, testTextFile1Name).catch(() => {
throw new Error(`Tests beforeAll(): Error while uploading file ${testTextFile1Name}`)
})
})

afterAll(async () => {
try {
await deleteUnpublishedDatasetViaApi(testDatasetIds.numericId)
} catch (error) {
throw new Error('Tests afterAll(): Error while deleting test dataset')
}

try {
await deleteCollectionViaApi(testCollectionAlias)
} catch (error) {
throw new Error('Tests afterAll(): Error while deleting test collection')
}
})

test('should successfully update categories of a file', async () => {
const datasetFiles = await getDatasetFiles.execute(testDatasetIds.numericId)
const fileId = datasetFiles.files[0].id

try {
await updateFileCategories.execute(fileId, metadataUpdate)
} catch (error) {
throw new Error('File metadata should be updated')
} finally {
const fileInfo: FileModel = (await getFile.execute(
fileId,
DatasetNotNumberedVersion.LATEST
)) as FileModel

expect(fileInfo.categories).toEqual(metadataUpdate)
}
})

test('should successfully update categories of a file with replace parameter', async () => {
const datasetFiles = await getDatasetFiles.execute(testDatasetIds.numericId)
const fileId = datasetFiles.files[0].id
const newCategories = ['new Category']
try {
await updateFileCategories.execute(fileId, newCategories, true)
} catch (error) {
throw new Error('File metadata should be updated')
} finally {
const fileInfo: FileModel = (await getFile.execute(
fileId,
DatasetNotNumberedVersion.LATEST
)) as FileModel

expect(fileInfo.categories).toEqual(newCategories)
}
})

test('should not duplicate categories when merging', async () => {
const datasetFiles = await getDatasetFiles.execute(testDatasetIds.numericId)
const fileId = datasetFiles.files[0].id

const initialCategories = ['Category 1', 'Category 2']
const newCategories = ['Category 2', 'Category 3']
const expectedMergedCategories = ['Category 1', 'Category 2', 'Category 3']

await updateFileCategories.execute(fileId, initialCategories, true)
await updateFileCategories.execute(fileId, newCategories, false)

const fileInfo = (await getFile.execute(fileId, DatasetNotNumberedVersion.LATEST)) as FileModel

expect(fileInfo.categories?.sort()).toEqual(expectedMergedCategories.sort())
})

test('should replace categories when replace = true', async () => {
const datasetFiles = await getDatasetFiles.execute(testDatasetIds.numericId)
const fileId = datasetFiles.files[0].id

const initialCategories = ['Category 1', 'Category 2', 'Category 3']
const newCategories = ['Category 4', 'Category 5', 'Category 6']

await updateFileCategories.execute(fileId, initialCategories, true)
await updateFileCategories.execute(fileId, newCategories, true)

const fileInfo = (await getFile.execute(fileId, DatasetNotNumberedVersion.LATEST)) as FileModel

expect(fileInfo.categories?.sort()).toEqual(newCategories.sort())
})

test('should throw an error when the file id does not exist', async () => {
let writeError: WriteError | undefined = undefined
const nonExistentFileId = 5

try {
await updateFileCategories.execute(nonExistentFileId, metadataUpdate)
throw new Error('Use case should throw an error')
} catch (error) {
writeError = error as WriteError
} finally {
expect(writeError).toBeInstanceOf(WriteError)
expect(writeError?.message).toEqual(
`There was an error when writing the resource. Reason was: [404] File with ID ${nonExistentFileId} not found.`
)
}
})
})
Loading