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
30 changes: 30 additions & 0 deletions docs/useCases.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ The different use cases currently available in the package are classified below,
- [Create a Dataset](#create-a-dataset)
- [Update a Dataset](#update-a-dataset)
- [Publish a Dataset](#publish-a-dataset)
- [Deaccession a Dataset](#deaccession-a-dataset)
- [Files](#Files)
- [Files read use cases](#files-read-use-cases)
- [Get a File](#get-a-file)
Expand Down Expand Up @@ -753,6 +754,35 @@ The `versionUpdateType` parameter can be a [VersionUpdateType](../src/datasets/d
- `VersionUpdateType.MAJOR`
- `VersionUpdateType.UPDATE_CURRENT`

#### Deaccession a Dataset

Deaccession a Dataset, given its identifier, version, and deaccessionDatasetDTO to perform.

##### Example call:

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

/* ... */

const datasetId = 1
const version = ':latestPublished'
const deaccessionDatasetDTO = {
deaccessionReason: 'Description of the deaccession reason.',
deaccessionForwardURL: 'https://demo.dataverse.org'
}

deaccessionDataset.execute(datasetId, version, deaccessionDatasetDTO)

/* ... */
```

_See [use case](../src/datasets/domain/useCases/DeaccessionDataset.ts) implementation_.
The `datasetId` parameter can be a string for persistent identifiers, or a number for numeric identifiers.
The `version` parameter should be a string or a [DatasetNotNumberedVersion](../src/datasets/domain/models/DatasetNotNumberedVersion.ts) enum value.

You cannot deaccession a dataset more than once. If you call this endpoint twice for the same dataset version, you will get a not found error on the second call, since the dataset you are looking for will no longer be published since it is already deaccessioned.

## Files

### Files read use cases
Expand Down
4 changes: 4 additions & 0 deletions src/datasets/domain/dtos/DatasetDeaccessionDTO.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface DatasetDeaccessionDTO {
deaccessionReason: string
deaccessionForwardURL?: string
}
6 changes: 6 additions & 0 deletions src/datasets/domain/repositories/IDatasetsRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { DatasetPreviewSubset } from '../models/DatasetPreviewSubset'
import { DatasetUserPermissions } from '../models/DatasetUserPermissions'
import { CreatedDatasetIdentifiers } from '../models/CreatedDatasetIdentifiers'
import { DatasetDTO } from '../dtos/DatasetDTO'
import { DatasetDeaccessionDTO } from '../dtos/DatasetDeaccessionDTO'
import { MetadataBlock } from '../../../metadataBlocks'
import { DatasetVersionDiff } from '../models/DatasetVersionDiff'

Expand Down Expand Up @@ -45,4 +46,9 @@ export interface IDatasetsRepository {
dataset: DatasetDTO,
datasetMetadataBlocks: MetadataBlock[]
): Promise<void>
deaccessionDataset(
datasetId: number | string,
datasetVersionId: string,
deaccessionDTO: DatasetDeaccessionDTO
): Promise<void>
}
31 changes: 31 additions & 0 deletions src/datasets/domain/useCases/DeaccessionDataset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { UseCase } from '../../../core/domain/useCases/UseCase'
import { IDatasetsRepository } from '../repositories/IDatasetsRepository'
import { DatasetDeaccessionDTO } from '../dtos/DatasetDeaccessionDTO'
import { DatasetNotNumberedVersion } from '../models/DatasetNotNumberedVersion'

export class DeaccessionDataset implements UseCase<void> {
private datasetsRepository: IDatasetsRepository

constructor(datasetsRepository: IDatasetsRepository) {
this.datasetsRepository = datasetsRepository
}

/**
* Deaccession a dataset, given a dataset id, a dataset version id, and a DatasetDeaccessionDTO object.
* @param {number | string} [datasetId] - The dataset identifier, which can be a string (for persistent identifiers), or a number (for numeric identifiers).
* @param {string | DatasetNotNumberedVersion} [datasetVersionId] - The dataset version identifier, which can be a version-specific numeric string (for example, 1.0) or a DatasetNotNumberedVersion enum value.
* @returns A promise that resolves when the dataset is deaccessioned
* @throws An error if the dataset could not be deaccessioned
*/
async execute(
datasetId: number | string,
datasetVersionId: string | DatasetNotNumberedVersion,
DatasetDeaccessionDTO: DatasetDeaccessionDTO
): Promise<void> {
return await this.datasetsRepository.deaccessionDataset(
datasetId,
datasetVersionId,
DatasetDeaccessionDTO
)
}
}
6 changes: 5 additions & 1 deletion src/datasets/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { MultipleMetadataFieldValidator } from './domain/useCases/validators/Mul
import { PublishDataset } from './domain/useCases/PublishDataset'
import { UpdateDataset } from './domain/useCases/UpdateDataset'
import { GetDatasetVersionDiff } from './domain/useCases/GetDatasetVersionDiff'
import { DeaccessionDataset } from './domain/useCases/DeaccessionDataset'

const datasetsRepository = new DatasetsRepository()

Expand Down Expand Up @@ -46,6 +47,7 @@ const updateDataset = new UpdateDataset(
metadataBlocksRepository,
datasetResourceValidator
)
const deaccessionDataset = new DeaccessionDataset(datasetsRepository)

export {
getDataset,
Expand All @@ -59,7 +61,8 @@ export {
getDatasetVersionDiff,
publishDataset,
createDataset,
updateDataset
updateDataset,
deaccessionDataset
}
export { DatasetNotNumberedVersion } from './domain/models/DatasetNotNumberedVersion'
export { DatasetUserPermissions } from './domain/models/DatasetUserPermissions'
Expand All @@ -85,5 +88,6 @@ export {
DatasetMetadataBlockValuesDTO,
DatasetMetadataChildFieldValueDTO
} from './domain/dtos/DatasetDTO'
export { DatasetDeaccessionDTO } from './domain/dtos/DatasetDeaccessionDTO'
export { CreatedDatasetIdentifiers } from './domain/models/CreatedDatasetIdentifiers'
export { VersionUpdateType } from './domain/models/Dataset'
20 changes: 20 additions & 0 deletions src/datasets/infra/repositories/DatasetsRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { DatasetLock } from '../../domain/models/DatasetLock'
import { CreatedDatasetIdentifiers } from '../../domain/models/CreatedDatasetIdentifiers'
import { DatasetPreviewSubset } from '../../domain/models/DatasetPreviewSubset'
import { DatasetDTO } from '../../domain/dtos/DatasetDTO'
import { DatasetDeaccessionDTO } from '../../domain/dtos/DatasetDeaccessionDTO'
import { MetadataBlock } from '../../../metadataBlocks'
import { transformDatasetModelToNewDatasetRequestPayload } from './transformers/datasetTransformers'
import { transformDatasetLocksResponseToDatasetLocks } from './transformers/datasetLocksTransformers'
Expand Down Expand Up @@ -214,4 +215,23 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi
throw error
})
}

public async deaccessionDataset(
datasetId: string | number,
datasetVersionId: string,
deaccessionDTO: DatasetDeaccessionDTO
): Promise<void> {
return this.doPost(
this.buildApiEndpoint(
this.datasetsResourceName,
`versions/${datasetVersionId}/deaccession`,
datasetId
),
deaccessionDTO
)
.then(() => undefined)
.catch((error) => {
throw error
})
}
}
161 changes: 161 additions & 0 deletions test/functional/datasets/DeaccessionDataset.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import {
deaccessionDataset,
DatasetDeaccessionDTO,
createDataset,
publishDataset,
VersionUpdateType
} from '../../../src/datasets'
import { ApiConfig, WriteError } from '../../../src'
import { TestConstants } from '../../testHelpers/TestConstants'
import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'
import {
waitForNoLocks,
deletePublishedDatasetViaApi,
deleteUnpublishedDatasetViaApi
} from '../../testHelpers/datasets/datasetHelper'

const testDataset = {
license: {
name: 'CC0 1.0',
uri: 'http://creativecommons.org/publicdomain/zero/1.0',
iconUri: 'https://licensebuttons.net/p/zero/1.0/88x31.png'
},
metadataBlockValues: [
{
name: 'citation',
fields: {
title: 'Dataset created using the createDataset use case',
author: [
{
authorName: 'Admin, Dataverse',
authorAffiliation: 'Dataverse.org'
},
{
authorName: 'Owner, Dataverse',
authorAffiliation: 'Dataversedemo.org'
}
],
datasetContact: [
{
datasetContactEmail: '[email protected]',
datasetContactName: 'Finch, Fiona'
}
],
dsDescription: [
{
dsDescriptionValue: 'This is the description of the dataset.'
}
],
subject: ['Medicine, Health and Life Sciences']
}
}
]
}

describe('execute', () => {
beforeEach(async () => {
ApiConfig.init(
TestConstants.TEST_API_URL,
DataverseApiAuthMechanism.API_KEY,
process.env.TEST_API_KEY
)
})

test('should deaccession a dataset when required fields are sent', async () => {
const createdDatasetIdentifiers = await createDataset.execute(testDataset)

const response = await publishDataset.execute(
createdDatasetIdentifiers.persistentId,
VersionUpdateType.MAJOR
)
await waitForNoLocks(createdDatasetIdentifiers.numericId, 10)

expect(response).toBeUndefined()

const testDeaccessionDatasetDTO: DatasetDeaccessionDTO = {
deaccessionReason: 'Description of the deaccession reason.',
deaccessionForwardURL: 'https://demo.dataverse.org'
}

const actual = await deaccessionDataset.execute(
createdDatasetIdentifiers.numericId,
'1.0',
testDeaccessionDatasetDTO
)

expect(actual).toBeUndefined()

await deletePublishedDatasetViaApi(createdDatasetIdentifiers.persistentId)
})

test('should throw an error when the dataset id is incorrect', async () => {
const createdDatasetIdentifiers = await createDataset.execute(testDataset)

const testDeaccessionDatasetDTO: DatasetDeaccessionDTO = {
deaccessionReason: 'Description of the deaccession reason.',
deaccessionForwardURL: 'https://demo.dataverse.org'
}

await expect(
deaccessionDataset.execute(
createdDatasetIdentifiers.numericId,
':latest-published',
testDeaccessionDatasetDTO
)
).rejects.toThrow(Error)

await deleteUnpublishedDatasetViaApi(createdDatasetIdentifiers.numericId)
})

test('should not deaccession a dataset when it is not published', async () => {
const createdDatasetIdentifiers = await createDataset.execute(testDataset)
const testDeaccessionDatasetDTO: DatasetDeaccessionDTO = {
deaccessionReason: 'Description of the deaccession reason.'
}

await expect(
deaccessionDataset.execute(
createdDatasetIdentifiers.numericId,
':latest-published',
testDeaccessionDatasetDTO
)
).rejects.toBeInstanceOf(WriteError)

await deleteUnpublishedDatasetViaApi(createdDatasetIdentifiers.numericId)
})

test('should not deaccession a dataset when it is deaccessioned once', async () => {
const createdDatasetIdentifiers = await createDataset.execute(testDataset)

const response = await publishDataset.execute(
createdDatasetIdentifiers.persistentId,
VersionUpdateType.MAJOR
)
await waitForNoLocks(createdDatasetIdentifiers.numericId, 10)

expect(response).toBeUndefined()

const testDeaccessionDatasetDTO: DatasetDeaccessionDTO = {
deaccessionReason: 'Description of the deaccession reason.',
deaccessionForwardURL: 'https://demo.dataverse.org'
}

const actual = await deaccessionDataset.execute(
createdDatasetIdentifiers.numericId,
'1.0',
testDeaccessionDatasetDTO
)

expect(actual).toBeUndefined()

await expect(
deaccessionDataset.execute(
createdDatasetIdentifiers.numericId,
'1.0',
testDeaccessionDatasetDTO
)
).rejects.toThrow(Error)

await deletePublishedDatasetViaApi(createdDatasetIdentifiers.persistentId)
})
})
61 changes: 60 additions & 1 deletion test/integration/datasets/DatasetsRepository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import {
VersionUpdateType,
createDataset,
CreatedDatasetIdentifiers,
DatasetDTO
DatasetDTO,
DatasetDeaccessionDTO
} from '../../../src/datasets'
import { ApiConfig, WriteError } from '../../../src'
import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'
Expand Down Expand Up @@ -866,4 +867,62 @@ describe('DatasetsRepository', () => {
).rejects.toThrow(expectedError)
})
})

describe('deaccessionDataset', () => {
test('should deaccession a dataset', async () => {
const testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO)
await publishDatasetViaApi(testDatasetIds.numericId)
await waitForNoLocks(testDatasetIds.numericId, 10)

const deaccessionDTO: DatasetDeaccessionDTO = {
deaccessionReason: 'Deaccessioning the dataset for testing purposes'
}

const actual = await sut.deaccessionDataset(testDatasetIds.numericId, '1.0', deaccessionDTO)

expect(actual).toBeUndefined()

const dataset = await sut.getDataset(testDatasetIds.numericId, '1.0', true, false)

expect(dataset.versionInfo.state).toBe('DEACCESSIONED')
})

test('should return error when dataset is deaccessioned', async () => {
const testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO)
await publishDatasetViaApi(testDatasetIds.numericId)
await waitForNoLocks(testDatasetIds.numericId, 10)

const deaccessionDTO: DatasetDeaccessionDTO = {
deaccessionReason: 'Deaccessioning the dataset for testing purposes'
}

const actual = await sut.deaccessionDataset(testDatasetIds.numericId, '1.0', deaccessionDTO)

expect(actual).toBeUndefined()

await expect(
sut.deaccessionDataset(testDatasetIds.numericId, '1.0', deaccessionDTO)
).rejects.toBeInstanceOf(WriteError)
})

test('should return error when dataset is not published', async () => {
const testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO)

const deaccessionDTO: DatasetDeaccessionDTO = {
deaccessionReason: 'Deaccessioning the dataset for testing purposes'
}

await expect(
sut.deaccessionDataset(testDatasetIds.numericId, ':latest-published', deaccessionDTO)
).rejects.toBeInstanceOf(WriteError)
})

test('should return error when dataset does not exist', async () => {
await expect(
sut.deaccessionDataset(nonExistentTestDatasetId, '1.0', {
deaccessionReason: 'Deaccessioning the dataset for testing purposes'
})
).rejects.toBeInstanceOf(WriteError)
})
})
})
Loading