Skip to content

Commit

Permalink
Merge pull request #137 from IQSS/feature/133-add-publish-dataset-use…
Browse files Browse the repository at this point in the history
…-case

133 - Add publish dataset use case
  • Loading branch information
GPortas authored Apr 4, 2024
2 parents e0438a2 + d57ce4a commit c4971c2
Show file tree
Hide file tree
Showing 13 changed files with 380 additions and 17 deletions.
35 changes: 35 additions & 0 deletions docs/useCases.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ The different use cases currently available in the package are classified below,
- [List All Datasets](#list-all-datasets)
- [Datasets write use cases](#datasets-write-use-cases)
- [Create a Dataset](#create-a-dataset)
- [Publish a Dataset](#publish-a-dataset)
- [Files](#Files)
- [Files read use cases](#files-read-use-cases)
- [Get a File](#get-a-file)
Expand Down Expand Up @@ -356,6 +357,40 @@ The above example creates the new dataset in the `root` collection since no coll

The use case returns a [CreatedDatasetIdentifiers](../src/datasets/domain/models/CreatedDatasetIdentifiers.ts) object, which includes the persistent and numeric identifiers of the created dataset.

#### Publish a Dataset

Publishes a Dataset, given its identifier and the type of version update to perform.

##### Example call:

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

/* ... */

const datasetId = 'doi:10.77777/FK2/AAAAAA'
const versionUpdateType = VersionUpdateType.MINOR

publishDataset.execute(datasetId, versionUpdateType).then((publishedDataset: Dataset) => {
/* ... */
})

/* ... */
```

_See [use case](../src/datasets/domain/useCases/PublishDataset.ts) implementation_.

The above example publishes the dataset with the specified identifier and performs a minor version update. If the response
is successful, the use case does not return the dataset object, but the HTTP status code `200`. Otherwise, it throws an error.
If you want to perform a major version update, you must set the `versionUpdateType` parameter to `VersionUpdateType.MAJOR`.

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

The `versionUpdateType` parameter can be a [VersionUpdateType](../src/datasets/domain/models/VersionUpdateType.ts) enum value, which can be one of the following:

- `VersionUpdateType.MINOR`
- `VersionUpdateType.MAJOR`

## Files

### Files read use cases
Expand Down
5 changes: 5 additions & 0 deletions src/datasets/domain/models/Dataset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,8 @@ interface Software extends DatasetMetadataSubField {
softwareName?: string
softwareVersion?: string
}

export enum VersionUpdateType {
MAJOR = 'major',
MINOR = 'minor'
}
3 changes: 2 additions & 1 deletion src/datasets/domain/repositories/IDatasetsRepository.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Dataset } from '../models/Dataset'
import { Dataset, VersionUpdateType } from '../models/Dataset'
import { DatasetLock } from '../models/DatasetLock'
import { DatasetPreviewSubset } from '../models/DatasetPreviewSubset'
import { DatasetUserPermissions } from '../models/DatasetUserPermissions'
Expand Down Expand Up @@ -32,4 +32,5 @@ export interface IDatasetsRepository {
datasetMetadataBlocks: MetadataBlock[],
collectionId: string
): Promise<CreatedDatasetIdentifiers>
publishDataset(datasetId: number | string, versionUpdateType: VersionUpdateType): Promise<void>
}
22 changes: 22 additions & 0 deletions src/datasets/domain/useCases/PublishDataset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { UseCase } from '../../../core/domain/useCases/UseCase'
import { VersionUpdateType } from '../models/Dataset'
import { IDatasetsRepository } from '../repositories/IDatasetsRepository' // Assuming Axios for HTTP requests

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

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

/**
* Publishes a dataset, given its identifier and the type of version update type.
*
* @param {number | string} [datasetId] - The dataset identifier, which can be a string (for persistent identifiers), or a number (for numeric identifiers).
* @param {VersionUpdateType} versionUpdateType - Specifies the type of version update, 'major' or 'minor'.
* @returns {Promise<void>} - This method does not return anything upon successful completion.
*/
async execute(datasetId: number | string, versionUpdateType: VersionUpdateType): Promise<void> {
return await this.datasetsRepository.publishDataset(datasetId, versionUpdateType)
}
}
6 changes: 5 additions & 1 deletion src/datasets/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { GetDatasetSummaryFieldNames } from './domain/useCases/GetDatasetSummary
import { GetPrivateUrlDatasetCitation } from './domain/useCases/GetPrivateUrlDatasetCitation'
import { SingleMetadataFieldValidator } from './domain/useCases/validators/SingleMetadataFieldValidator'
import { MultipleMetadataFieldValidator } from './domain/useCases/validators/MultipleMetadataFieldValidator'
import { PublishDataset } from './domain/useCases/PublishDataset'

const datasetsRepository = new DatasetsRepository()

Expand All @@ -34,6 +35,7 @@ const createDataset = new CreateDataset(
new MetadataBlocksRepository(),
new NewDatasetResourceValidator(metadataFieldValidator)
)
const publishDataset = new PublishDataset(datasetsRepository)

export {
getDataset,
Expand All @@ -44,7 +46,8 @@ export {
getDatasetUserPermissions,
getDatasetSummaryFieldNames,
getPrivateUrlDatasetCitation,
createDataset
createDataset,
publishDataset
}
export { DatasetNotNumberedVersion } from './domain/models/DatasetNotNumberedVersion'
export { DatasetUserPermissions } from './domain/models/DatasetUserPermissions'
Expand All @@ -70,3 +73,4 @@ export {
NewDatasetMetadataChildFieldValueDTO as NewDatasetMetadataChildFieldValue
} from './domain/dtos/NewDatasetDTO'
export { CreatedDatasetIdentifiers } from './domain/models/CreatedDatasetIdentifiers'
export { VersionUpdateType } from './domain/models/Dataset'
25 changes: 21 additions & 4 deletions src/datasets/infra/repositories/DatasetsRepository.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { ApiRepository } from '../../../core/infra/repositories/ApiRepository'
import { IDatasetsRepository } from '../../domain/repositories/IDatasetsRepository'
import { Dataset } from '../../domain/models/Dataset'
import { DatasetLock } from '../../domain/models/DatasetLock'
import { Dataset, VersionUpdateType } from '../../domain/models/Dataset'
import { transformVersionResponseToDataset } from './transformers/datasetTransformers'
import { DatasetUserPermissions } from '../../domain/models/DatasetUserPermissions'
import { transformDatasetUserPermissionsResponseToDatasetUserPermissions } from './transformers/datasetUserPermissionsTransformers'
import { DatasetLock } from '../../domain/models/DatasetLock'
import { CreatedDatasetIdentifiers } from '../../domain/models/CreatedDatasetIdentifiers'
import { DatasetPreviewSubset } from '../../domain/models/DatasetPreviewSubset'
import { NewDatasetDTO } from '../../domain/dtos/NewDatasetDTO'
import { MetadataBlock } from '../../../metadataBlocks'
import { transformVersionResponseToDataset } from './transformers/datasetTransformers'
import { transformNewDatasetModelToRequestPayload } from './transformers/newDatasetTransformers'
import { transformDatasetLocksResponseToDatasetLocks } from './transformers/datasetLocksTransformers'
import { transformDatasetPreviewsResponseToDatasetPreviewSubset } from './transformers/datasetPreviewsTransformers'
import { transformDatasetUserPermissionsResponseToDatasetUserPermissions } from './transformers/datasetUserPermissionsTransformers'

export interface GetAllDatasetPreviewsQueryParams {
per_page?: number
Expand Down Expand Up @@ -157,4 +157,21 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi
throw error
})
}

public async publishDataset(
datasetId: string | number,
versionUpdateType: VersionUpdateType
): Promise<void> {
return this.doPost(
this.buildApiEndpoint(this.datasetsResourceName, `actions/:publish`, datasetId),
{},
{
type: versionUpdateType
}
)
.then(() => undefined)
.catch((error) => {
throw error
})
}
}
7 changes: 6 additions & 1 deletion test/environment/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import { TestConstants } from '../testHelpers/TestConstants'
import datasetJson1 from '../testHelpers/datasets/test-dataset-1.json'
import datasetJson2 from '../testHelpers/datasets/test-dataset-2.json'
import datasetJson3 from '../testHelpers/datasets/test-dataset-3.json'
import datasetJson4 from '../testHelpers/datasets/test-dataset-4.json'
import collectionJson from '../testHelpers/collections/test-collection-1.json'
import { ROOT_COLLECTION_ALIAS } from '../../src/collections/domain/models/Collection'

const NUMBER_OF_DATASETS = 4
const COMPOSE_FILE = 'docker-compose.yml'

const CONTAINER_DATAVERSE_BOOTSTRAP_NAME = 'test_dataverse_bootstrap'
Expand Down Expand Up @@ -63,6 +65,9 @@ async function setupTestFixtures(): Promise<void> {
await createDatasetViaApi(datasetJson2).catch(() => {
console.error('Tests setup: Error while creating test Dataset 2')
})
await createDatasetViaApi(datasetJson4).catch(() => {
console.error('Tests setup: Error while creating test Dataset 4')
})
await createCollectionViaApi(collectionJson)
.then()
.catch(() => {
Expand Down Expand Up @@ -108,7 +113,7 @@ async function waitForDatasetsIndexingInSolr(): Promise<void> {
.get(`${TestConstants.TEST_API_URL}/search?q=*&type=dataset`, buildRequestHeaders())
.then((response) => {
const nDatasets = response.data.data.items.length
if (nDatasets === 3) {
if (nDatasets === NUMBER_OF_DATASETS) {
datasetsIndexed = true
}
})
Expand Down
78 changes: 78 additions & 0 deletions test/functional/datasets/PublishDataset.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import {
ApiConfig,
createDataset,
publishDataset,
VersionUpdateType,
WriteError
} from '../../../src'
import { TestConstants } from '../../testHelpers/TestConstants'
import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'
import { waitForNoLocks } from '../../testHelpers/datasets/datasetHelper'

const testNewDataset = {
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: 'finch@mailinator.com',
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 successfully publish a dataset', async () => {
const dataset = await createDataset.execute(testNewDataset)

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

expect(response).toBeUndefined()
})

test('should throw an error when trying to publish a dataset that does not exist', async () => {
const nonExistentTestDatasetId = 'non-existent-dataset'
const expectedError = new WriteError(
`[404] Dataset with Persistent ID ${nonExistentTestDatasetId} not found.`
)

await expect(
publishDataset.execute(nonExistentTestDatasetId, VersionUpdateType.MAJOR)
).rejects.toThrow(expectedError)
})
})
46 changes: 38 additions & 8 deletions test/integration/datasets/DatasetsRepository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@ import { DatasetsRepository } from '../../../src/datasets/infra/repositories/Dat
import { TestConstants } from '../../testHelpers/TestConstants'
import {
createPrivateUrlViaApi,
publishDatasetViaApi,
deaccessionDatasetViaApi,
publishDatasetViaApi,
waitForNoLocks
} from '../../testHelpers/datasets/datasetHelper'
import { ReadError } from '../../../src/core/domain/repositories/ReadError'
import {
DatasetNotNumberedVersion,
DatasetLockType,
DatasetPreviewSubset
DatasetNotNumberedVersion,
DatasetPreviewSubset,
VersionUpdateType
} from '../../../src/datasets'
import { ApiConfig } from '../../../src'
import { ApiConfig, WriteError } from '../../../src'
import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'
import { MetadataBlocksRepository } from '../../../src/metadataBlocks/infra/repositories/MetadataBlocksRepository'
import {
Expand Down Expand Up @@ -46,7 +47,7 @@ describe('DatasetsRepository', () => {

describe('getAllDatasetPreviews', () => {
const testPageLimit = 1
const expectedTotalDatasetCount = 3
const expectedTotalDatasetCount = 4

test('should return all dataset previews when no pagination params are defined', async () => {
const actual: DatasetPreviewSubset = await sut.getAllDatasetPreviews()
Expand All @@ -65,20 +66,21 @@ describe('DatasetsRepository', () => {
test('should return second dataset preview page', async () => {
const actual = await sut.getAllDatasetPreviews(testPageLimit, 1)
expect(actual.datasetPreviews.length).toEqual(1)
expect(actual.datasetPreviews[0].title).toMatch('Second Dataset')
expect(actual.datasetPreviews[0].title).toMatch('Fourth Dataset')
expect(actual.totalDatasetCount).toEqual(expectedTotalDatasetCount)
})

test('should return third dataset preview page', async () => {
const actual = await sut.getAllDatasetPreviews(testPageLimit, 2)
expect(actual.datasetPreviews.length).toEqual(1)
expect(actual.datasetPreviews[0].title).toMatch('First Dataset')
expect(actual.datasetPreviews[0].title).toMatch('Second Dataset')
expect(actual.totalDatasetCount).toEqual(expectedTotalDatasetCount)
})

test('should return fourth dataset preview page', async () => {
const actual = await sut.getAllDatasetPreviews(testPageLimit, 3)
expect(actual.datasetPreviews.length).toEqual(0)
expect(actual.datasetPreviews.length).toEqual(1)
expect(actual.datasetPreviews[0].title).toMatch('First Dataset')
expect(actual.totalDatasetCount).toEqual(expectedTotalDatasetCount)
})

Expand Down Expand Up @@ -377,4 +379,32 @@ describe('DatasetsRepository', () => {
)
})
})

describe('publishDataset', () => {
test('should publish a new dataset version', async () => {
const expectedMajorVersion = 1
await waitForNoLocks(TestConstants.TEST_CREATED_DATASET_4_ID, 10)

await sut.publishDataset(TestConstants.TEST_CREATED_DATASET_4_ID, VersionUpdateType.MAJOR)
await waitForNoLocks(TestConstants.TEST_CREATED_DATASET_4_ID, 10)

const newDatasetVersion = await sut.getDataset(
TestConstants.TEST_CREATED_DATASET_4_ID,
latestVersionId,
false
)

expect(newDatasetVersion.versionInfo.majorNumber).toBe(expectedMajorVersion)
})

test('should return error when dataset does not exist', async () => {
const expectedError = new WriteError(
`[404] Dataset with ID ${nonExistentTestDatasetId} not found.`
)

await expect(
sut.publishDataset(nonExistentTestDatasetId, VersionUpdateType.MAJOR)
).rejects.toThrow(expectedError)
})
})
})
5 changes: 3 additions & 2 deletions test/testHelpers/TestConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,11 @@ export class TestConstants {
}
static readonly TEST_CREATED_DATASET_1_ID = 2
static readonly TEST_CREATED_DATASET_2_ID = 3
static readonly TEST_CREATED_DATASET_3_ID = 4
static readonly TEST_CREATED_DATASET_3_ID = 5
static readonly TEST_CREATED_DATASET_4_ID = 4
static readonly TEST_DUMMY_COLLECTION_ID = 10001
static readonly TEST_DUMMY_COLLECTION_ALIAS = 'dummyCollectionId'
static readonly TEST_CREATED_COLLECTION_1_ID = 4
static readonly TEST_CREATED_COLLECTION_1_ID = 5
static readonly TEST_CREATED_COLLECTION_1_ALIAS = 'firstCollection'
static readonly TEST_CREATED_COLLECTION_1_ROOT = ROOT_COLLECTION_ALIAS
}
Loading

0 comments on commit c4971c2

Please sign in to comment.