Skip to content

Commit 7f7cf9c

Browse files
committed
feat: use case for deaccession dataset
1 parent fef6bbc commit 7f7cf9c

File tree

9 files changed

+338
-5
lines changed

9 files changed

+338
-5
lines changed

Diff for: docs/useCases.md

+31
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ The different use cases currently available in the package are classified below,
3636
- [Create a Dataset](#create-a-dataset)
3737
- [Update a Dataset](#update-a-dataset)
3838
- [Publish a Dataset](#publish-a-dataset)
39+
- [Deaccession a Dataset](#deaccession-a-dataset)
3940
- [Files](#Files)
4041
- [Files read use cases](#files-read-use-cases)
4142
- [Get a File](#get-a-file)
@@ -753,6 +754,36 @@ The `versionUpdateType` parameter can be a [VersionUpdateType](../src/datasets/d
753754
- `VersionUpdateType.MAJOR`
754755
- `VersionUpdateType.UPDATE_CURRENT`
755756

757+
#### Deaccesion a Dataset
758+
759+
Deaccesion a Dataset, given its identifier, version, and deaccessionDatasetDTO to perform.
760+
761+
##### Example call:
762+
763+
```typescript
764+
import { deaccessionDataset } from '@iqss/dataverse-client-javascript'
765+
766+
/* ... */
767+
768+
const datasetId = 1
769+
const version = ':latestPublished'
770+
const deaccessionDatasetDTO = {
771+
deaccessionReason: 'Description of the deaccession reason.',
772+
deaccessionForwardURL: 'https://demo.dataverse.org'
773+
}
774+
775+
deaccessionDataset.execute(datasetId, version, deaccessionDatasetDTO)
776+
777+
/* ... */
778+
```
779+
780+
_See [use case](../src/datasets/domain/useCases/DeaccesionDataset.ts) implementation_.
781+
The `datasetId` parameter can be a string for persistent identifiers, or a number for numeric identifiers.
782+
The `version` parameter should be a string or a [DatasetNotNumberedVersion](../src/datasets/domain/models/DatasetNotNumberedVersion.ts) enum value. If not set, the default value is `DatasetNotNumberedVersion.LATEST`.
783+
In `deaccessionDatasetDTO`, deaccessionForwardURL is optional.
784+
785+
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.
786+
756787
## Files
757788

758789
### Files read use cases

Diff for: src/datasets/domain/dtos/DatasetDTO.ts

+5
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ export interface DatasetMetadataBlockValuesDTO {
1010
fields: DatasetMetadataFieldsDTO
1111
}
1212

13+
export interface DatasetDeaccessionDTO {
14+
deaccessionReason: string
15+
deaccessionForwardURL?: string
16+
}
17+
1318
export type DatasetMetadataFieldsDTO = Record<string, DatasetMetadataFieldValueDTO>
1419

1520
export type DatasetMetadataFieldValueDTO =

Diff for: src/datasets/domain/repositories/IDatasetsRepository.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { DatasetLock } from '../models/DatasetLock'
33
import { DatasetPreviewSubset } from '../models/DatasetPreviewSubset'
44
import { DatasetUserPermissions } from '../models/DatasetUserPermissions'
55
import { CreatedDatasetIdentifiers } from '../models/CreatedDatasetIdentifiers'
6-
import { DatasetDTO } from '../dtos/DatasetDTO'
6+
import { DatasetDTO, DatasetDeaccessionDTO } from '../dtos/DatasetDTO'
77
import { MetadataBlock } from '../../../metadataBlocks'
88
import { DatasetVersionDiff } from '../models/DatasetVersionDiff'
99

@@ -45,4 +45,9 @@ export interface IDatasetsRepository {
4545
dataset: DatasetDTO,
4646
datasetMetadataBlocks: MetadataBlock[]
4747
): Promise<void>
48+
deaccessionDataset(
49+
datasetId: number | string,
50+
datasetVersionId: string,
51+
deaccessionDTO: DatasetDeaccessionDTO
52+
): Promise<void>
4853
}

Diff for: src/datasets/domain/useCases/DeaccessionDataset.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { UseCase } from '../../../core/domain/useCases/UseCase'
2+
import { IDatasetsRepository } from '../repositories/IDatasetsRepository'
3+
import { DatasetDeaccessionDTO } from '../dtos/DatasetDTO'
4+
import { DatasetNotNumberedVersion } from '../models/DatasetNotNumberedVersion'
5+
6+
export class DeaccessionDataset implements UseCase<void> {
7+
private datasetsRepository: IDatasetsRepository
8+
9+
constructor(datasetsRepository: IDatasetsRepository) {
10+
this.datasetsRepository = datasetsRepository
11+
}
12+
13+
/**
14+
* Deaccession a dataset, given a dataset id, a dataset version id, and a DatasetDeaccessionDTO object.
15+
* @param {number | string} [datasetId] - The dataset identifier, which can be a string (for persistent identifiers), or a number (for numeric identifiers).
16+
* @param {string | DatasetNotNumberedVersion} [datasetVersionId=DatasetNotNumberedVersion.LATEST] - The dataset version identifier, which can be a version-specific numeric string (for example, 1.0) or a DatasetNotNumberedVersion enum value. If this parameter is not set, the default value is: DatasetNotNumberedVersion.LATEST
17+
* @param {DatasetDeaccessionDTO} [DatasetDeaccessionDTO] - The DatasetDeaccessionDTO object that contains the deaccession reason and an optional deaccession forward URL.
18+
* @returns A promise that resolves when the dataset is deaccessioned
19+
* @throws An error if the dataset could not be deaccessioned
20+
*/
21+
async execute(
22+
datasetId: number | string,
23+
datasetVersionId: string | DatasetNotNumberedVersion = DatasetNotNumberedVersion.LATEST,
24+
DatasetDeaccessionDTO: DatasetDeaccessionDTO
25+
): Promise<void> {
26+
return await this.datasetsRepository.deaccessionDataset(
27+
datasetId,
28+
datasetVersionId,
29+
DatasetDeaccessionDTO
30+
)
31+
}
32+
}

Diff for: src/datasets/index.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { MultipleMetadataFieldValidator } from './domain/useCases/validators/Mul
1616
import { PublishDataset } from './domain/useCases/PublishDataset'
1717
import { UpdateDataset } from './domain/useCases/UpdateDataset'
1818
import { GetDatasetVersionDiff } from './domain/useCases/GetDatasetVersionDiff'
19+
import { DeaccessionDataset } from './domain/useCases/DeaccessionDataset'
1920

2021
const datasetsRepository = new DatasetsRepository()
2122

@@ -46,6 +47,7 @@ const updateDataset = new UpdateDataset(
4647
metadataBlocksRepository,
4748
datasetResourceValidator
4849
)
50+
const deaccessionDataset = new DeaccessionDataset(datasetsRepository)
4951

5052
export {
5153
getDataset,
@@ -59,7 +61,8 @@ export {
5961
getDatasetVersionDiff,
6062
publishDataset,
6163
createDataset,
62-
updateDataset
64+
updateDataset,
65+
deaccessionDataset
6366
}
6467
export { DatasetNotNumberedVersion } from './domain/models/DatasetNotNumberedVersion'
6568
export { DatasetUserPermissions } from './domain/models/DatasetUserPermissions'
@@ -83,7 +86,8 @@ export {
8386
DatasetMetadataFieldsDTO,
8487
DatasetMetadataFieldValueDTO,
8588
DatasetMetadataBlockValuesDTO,
86-
DatasetMetadataChildFieldValueDTO
89+
DatasetMetadataChildFieldValueDTO,
90+
DatasetDeaccessionDTO
8791
} from './domain/dtos/DatasetDTO'
8892
export { CreatedDatasetIdentifiers } from './domain/models/CreatedDatasetIdentifiers'
8993
export { VersionUpdateType } from './domain/models/Dataset'

Diff for: src/datasets/infra/repositories/DatasetsRepository.ts

+20-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { transformDatasetUserPermissionsResponseToDatasetUserPermissions } from
1010
import { DatasetLock } from '../../domain/models/DatasetLock'
1111
import { CreatedDatasetIdentifiers } from '../../domain/models/CreatedDatasetIdentifiers'
1212
import { DatasetPreviewSubset } from '../../domain/models/DatasetPreviewSubset'
13-
import { DatasetDTO } from '../../domain/dtos/DatasetDTO'
13+
import { DatasetDTO, DatasetDeaccessionDTO } from '../../domain/dtos/DatasetDTO'
1414
import { MetadataBlock } from '../../../metadataBlocks'
1515
import { transformDatasetModelToNewDatasetRequestPayload } from './transformers/datasetTransformers'
1616
import { transformDatasetLocksResponseToDatasetLocks } from './transformers/datasetLocksTransformers'
@@ -214,4 +214,23 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi
214214
throw error
215215
})
216216
}
217+
218+
public async deaccessionDataset(
219+
datasetId: string | number,
220+
datasetVersionId: string,
221+
deaccessionDTO: DatasetDeaccessionDTO
222+
): Promise<void> {
223+
return this.doPost(
224+
this.buildApiEndpoint(
225+
this.datasetsResourceName,
226+
`versions/${datasetVersionId}/deaccession`,
227+
datasetId
228+
),
229+
deaccessionDTO
230+
)
231+
.then(() => undefined)
232+
.catch((error) => {
233+
throw error
234+
})
235+
}
217236
}

Diff for: test/functional/datasets/DeaccessionDataset.test.ts

+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import {
2+
deaccessionDataset,
3+
DatasetDeaccessionDTO,
4+
createDataset,
5+
publishDataset,
6+
VersionUpdateType
7+
} from '../../../src/datasets'
8+
import { ApiConfig, WriteError } from '../../../src'
9+
import { TestConstants } from '../../testHelpers/TestConstants'
10+
import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'
11+
import {
12+
waitForNoLocks,
13+
deletePublishedDatasetViaApi,
14+
deleteUnpublishedDatasetViaApi
15+
} from '../../testHelpers/datasets/datasetHelper'
16+
17+
const testDataset = {
18+
license: {
19+
name: 'CC0 1.0',
20+
uri: 'http://creativecommons.org/publicdomain/zero/1.0',
21+
iconUri: 'https://licensebuttons.net/p/zero/1.0/88x31.png'
22+
},
23+
metadataBlockValues: [
24+
{
25+
name: 'citation',
26+
fields: {
27+
title: 'Dataset created using the createDataset use case',
28+
author: [
29+
{
30+
authorName: 'Admin, Dataverse',
31+
authorAffiliation: 'Dataverse.org'
32+
},
33+
{
34+
authorName: 'Owner, Dataverse',
35+
authorAffiliation: 'Dataversedemo.org'
36+
}
37+
],
38+
datasetContact: [
39+
{
40+
datasetContactEmail: '[email protected]',
41+
datasetContactName: 'Finch, Fiona'
42+
}
43+
],
44+
dsDescription: [
45+
{
46+
dsDescriptionValue: 'This is the description of the dataset.'
47+
}
48+
],
49+
subject: ['Medicine, Health and Life Sciences']
50+
}
51+
}
52+
]
53+
}
54+
55+
describe('execute', () => {
56+
beforeEach(async () => {
57+
ApiConfig.init(
58+
TestConstants.TEST_API_URL,
59+
DataverseApiAuthMechanism.API_KEY,
60+
process.env.TEST_API_KEY
61+
)
62+
})
63+
64+
test('should deaccession a dataset when required fields are sent', async () => {
65+
const createdDatasetIdentifiers = await createDataset.execute(testDataset)
66+
67+
const response = await publishDataset.execute(
68+
createdDatasetIdentifiers.persistentId,
69+
VersionUpdateType.MAJOR
70+
)
71+
await waitForNoLocks(createdDatasetIdentifiers.numericId, 10)
72+
73+
expect(response).toBeUndefined()
74+
75+
const testDeaccessionDataset: DatasetDeaccessionDTO = {
76+
deaccessionReason: 'Description of the deaccession reason.',
77+
deaccessionForwardURL: 'https://demo.dataverse.org'
78+
}
79+
80+
const actual = await deaccessionDataset.execute(
81+
createdDatasetIdentifiers.numericId,
82+
'1.0',
83+
testDeaccessionDataset
84+
)
85+
86+
expect(actual).toBeUndefined()
87+
88+
await deletePublishedDatasetViaApi(createdDatasetIdentifiers.persistentId)
89+
})
90+
91+
test('should throw an error when the dataset id is incorrect', async () => {
92+
const createdDatasetIdentifiers = await createDataset.execute(testDataset)
93+
94+
const testDeaccessionDataset: DatasetDeaccessionDTO = {
95+
deaccessionReason: 'Description of the deaccession reason.',
96+
deaccessionForwardURL: 'https://demo.dataverse.org'
97+
}
98+
99+
await expect(
100+
deaccessionDataset.execute(
101+
createdDatasetIdentifiers.numericId,
102+
':latest-published',
103+
testDeaccessionDataset
104+
)
105+
).rejects.toThrow(Error)
106+
107+
await deleteUnpublishedDatasetViaApi(createdDatasetIdentifiers.numericId)
108+
})
109+
110+
test('should not deaccession a dataset when it is not published', async () => {
111+
const createdDatasetIdentifiers = await createDataset.execute(testDataset)
112+
const testDeaccessionDataset: DatasetDeaccessionDTO = {
113+
deaccessionReason: 'Description of the deaccession reason.'
114+
}
115+
116+
await expect(
117+
deaccessionDataset.execute(
118+
createdDatasetIdentifiers.numericId,
119+
':latest-published',
120+
testDeaccessionDataset
121+
)
122+
).rejects.toBeInstanceOf(WriteError)
123+
})
124+
125+
test('should deaccession a dataset when it is deaccessioned once', async () => {
126+
const createdDatasetIdentifiers = await createDataset.execute(testDataset)
127+
128+
const response = await publishDataset.execute(
129+
createdDatasetIdentifiers.persistentId,
130+
VersionUpdateType.MAJOR
131+
)
132+
await waitForNoLocks(createdDatasetIdentifiers.numericId, 10)
133+
134+
expect(response).toBeUndefined()
135+
136+
const testDeaccessionDataset: DatasetDeaccessionDTO = {
137+
deaccessionReason: 'Description of the deaccession reason.',
138+
deaccessionForwardURL: 'https://demo.dataverse.org'
139+
}
140+
141+
const actual = await deaccessionDataset.execute(
142+
createdDatasetIdentifiers.numericId,
143+
'1.0',
144+
testDeaccessionDataset
145+
)
146+
147+
expect(actual).toBeUndefined()
148+
149+
await expect(
150+
deaccessionDataset.execute(createdDatasetIdentifiers.numericId, '1.0', testDeaccessionDataset)
151+
).rejects.toThrow(Error)
152+
153+
await deleteUnpublishedDatasetViaApi(createdDatasetIdentifiers.numericId)
154+
})
155+
})

0 commit comments

Comments
 (0)