Skip to content
Open
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ This changelog follows the principles of [Keep a Changelog](https://keepachangel

### Added

- Datasets: Added public use cases for allowed curation labels, current status, status history, setting/removing labels, and submitting drafts for review, with numeric and persistent identifier support.

### Changed

### Fixed
Expand Down
116 changes: 116 additions & 0 deletions docs/useCases.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ The different use cases currently available in the package are classified below,
- [Unset Template As Default](#unset-template-as-default)
- [Datasets](#Datasets)
- [Datasets read use cases](#datasets-read-use-cases)
- [Get Allowed Dataset Curation Labels](#get-allowed-dataset-curation-labels)
- [Get Dataset Curation Status](#get-dataset-curation-status)
- [Get Dataset Curation Status History](#get-dataset-curation-status-history)
- [Get a Dataset](#get-a-dataset)
- [Get Dataset By Private URL Token](#get-dataset-by-private-url-token)
- [Get Dataset Citation Text](#get-dataset-citation-text)
Expand All @@ -65,6 +68,9 @@ The different use cases currently available in the package are classified below,
- [Get Dataset Available Dataset Type](#get-dataset-available-dataset-type)
- [Get Dataset Upload Limits](#get-dataset-upload-limits)
- [Datasets write use cases](#datasets-write-use-cases)
- [Set Dataset Curation Status](#set-dataset-curation-status)
- [Delete Dataset Curation Status](#delete-dataset-curation-status)
- [Submit Dataset For Review](#submit-dataset-for-review)
- [Create a Dataset](#create-a-dataset)
- [Update a Dataset](#update-a-dataset)
- [Update a Dataset License](#update-a-dataset-license)
Expand Down Expand Up @@ -967,6 +973,65 @@ _See [use case](../src/templates/domain/useCases/UpdateTemplateTermsOfAccess.ts)

### Datasets Read Use Cases

Curation labels require the server setting `:AllowedCurationLabels`, a JSON object mapping label-set names to arrays of labels, for example `{"defaultSet": ["Privacy Review", "Final Approval"]}`. A superuser selects a label set on a collection or dataset using the administration APIs. An explicit dataset selection overrides its collection. `DEFAULT` (or no selection) inherits from the parent collection up to the root; if no set is selected there, labels are disabled. `DISABLED` explicitly disables labels. Defining the setting alone does not select a set. Labels currently allow letters, digits, underscores, and spaces. These administration operations are not exposed by these use cases.

Allowed-label reads and status mutations require publish permission. Current-status and history reads also require publish permission by default. With `dataverse.ui.show-curation-status-to-all=true`, users with permission to view the draft can read them. The backend enforces these permissions; the SDK attaches the configured API key, session cookie, or bearer token.

Labels only track curation progress: assigning, changing, or removing one does not submit for review, return to the author, publish, or change review locks. Publication clears the draft label. `DatasetCurationStatus` and `DatasetReviewSubmission` are exported from the package root.

See the [Dataverse curation API guide](https://guides.dataverse.org/en/latest/api/curation-labels.html) for server configuration and API behavior.

#### Get Allowed Dataset Curation Labels

Returns the effective configured labels in server order. Requires publish permission. Labels are returned unchanged, including whitespace.

The `datasetId` accepts a numeric ID or persistent identifier. Configure authentication with `ApiConfig` before calling the use case. Backend failures propagate as `ReadError`.

##### Example call:

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

const datasetId = 'doi:10.77777/FK2/AAAAAA'
await getAllowedDatasetCurationLabels.execute(datasetId) // Promise<string[]>
```

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

#### Get Dataset Curation Status

Returns the current curation record, or `null` when no record exists. A removal is a record with no `label` (`undefined` when read); it retains its timestamp and assigner.

The `datasetId` accepts a numeric ID or persistent identifier. Configure authentication with `ApiConfig` before calling the use case. Backend failures propagate as `ReadError`.

##### Example call:

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

const datasetId = 'doi:10.77777/FK2/AAAAAA'
await getDatasetCurationStatus.execute(datasetId) // Promise<DatasetCurationStatus | null>
```

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

#### Get Dataset Curation Status History

Returns all assignments and removals, newest first, or an empty array when no history exists. Missing legacy `createTime` and `assigner` fields remain `undefined`. Timestamps remain server strings without timezone conversion.

The `datasetId` accepts a numeric ID or persistent identifier. Configure authentication with `ApiConfig` before calling the use case. Backend failures propagate as `ReadError`.

##### Example call:

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

const datasetId = 'doi:10.77777/FK2/AAAAAA'
await getDatasetCurationStatusHistory.execute(datasetId) // Promise<DatasetCurationStatus[]>
```

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

#### Get a Dataset

Returns a [Dataset](../src/datasets/domain/models/Dataset.ts) instance, given the search parameters to identify it.
Expand Down Expand Up @@ -1423,6 +1488,57 @@ Example returned value:

### Datasets Write Use Cases

#### Set Dataset Curation Status

Assigns or changes a configured label on a draft. Requires publish permission. Pass the exact value returned by `getAllowedDatasetCurationLabels`; reserved characters are safely encoded. Unconfigured labels, unchanged labels, and datasets without a draft are rejected by the backend.

The `datasetId` accepts a numeric ID or persistent identifier. Configure authentication with `ApiConfig` before calling the use case. Backend failures propagate as `WriteError`.

##### Example call:

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

const datasetId = 'doi:10.77777/FK2/AAAAAA'
await setDatasetCurationStatus.execute(datasetId, 'Privacy Review') // Promise<void>
```

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

#### Delete Dataset Curation Status

Removes the current draft label and retains a removal record in its history. Requires publish permission. This does not return the dataset to its author or remove review locks.

The `datasetId` accepts a numeric ID or persistent identifier. Configure authentication with `ApiConfig` before calling the use case. Backend failures propagate as `WriteError`.

##### Example call:

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

const datasetId = 'doi:10.77777/FK2/AAAAAA'
await deleteDatasetCurationStatus.execute(datasetId) // Promise<void>
```

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

#### Submit Dataset For Review

Submits a draft for review and returns `{ inReview: boolean, message: string }`. Requires an authenticated user with edit permission; anonymous submission is not supported by the backend. This adds a review lock and notifies curators. Drafts already in review and datasets without a draft are rejected.

The `datasetId` accepts a numeric ID or persistent identifier. Configure authentication with `ApiConfig` before calling the use case. Backend failures propagate as `WriteError`.

##### Example call:

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

const datasetId = 'doi:10.77777/FK2/AAAAAA'
await submitDatasetForReview.execute(datasetId) // Promise<DatasetReviewSubmission>
```

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

#### Create a Dataset

Creates a new Dataset in a collection, given a [DatasetDTO](../src/datasets/domain/dtos/DatasetDTO.ts) object, an optional collection identifier, which defaults to `:root`, and an optional dataset type.
Expand Down
10 changes: 10 additions & 0 deletions src/datasets/domain/models/DatasetCuration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface DatasetCurationStatus {
label?: string
createTime?: string
assigner?: string
}

export interface DatasetReviewSubmission {
inReview: boolean
message: string
}
8 changes: 8 additions & 0 deletions src/datasets/domain/repositories/IDatasetsRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,16 @@ import { DatasetUploadLimits } from '../models/DatasetUploadLimits'
import { DatasetReview } from '../models/DatasetReview'
import { ExportedDatasetMetadata } from '../models/ExportedDatasetMetadata'
import { DatasetNotNumberedVersion } from '../models/DatasetNotNumberedVersion'
import { DatasetCurationStatus, DatasetReviewSubmission } from '../models/DatasetCuration'

export interface IDatasetsRepository {
getAllowedDatasetCurationLabels(datasetId: number | string): Promise<string[]>
getDatasetCurationStatus(datasetId: number | string): Promise<DatasetCurationStatus | null>
getDatasetCurationStatusHistory(datasetId: number | string): Promise<DatasetCurationStatus[]>
setDatasetCurationStatus(datasetId: number | string, label: string): Promise<void>
deleteDatasetCurationStatus(datasetId: number | string): Promise<void>
submitDatasetForReview(datasetId: number | string): Promise<DatasetReviewSubmission>

getDataset(
datasetId: number | string,
datasetVersionId: string,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { UseCase } from '../../../../core/domain/useCases/UseCase'
import { IDatasetsRepository } from '../../repositories/IDatasetsRepository'

export class DeleteDatasetCurationStatus implements UseCase<void> {
constructor(private readonly datasetsRepository: IDatasetsRepository) {}

/**
* Removes the label from a draft, retaining history. Requires publish permission.
* @param datasetId Numeric dataset ID or persistent identifier.
*/
async execute(datasetId: number | string): Promise<void> {
return await this.datasetsRepository.deleteDatasetCurationStatus(datasetId)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { UseCase } from '../../../../core/domain/useCases/UseCase'
import { IDatasetsRepository } from '../../repositories/IDatasetsRepository'

export class GetAllowedDatasetCurationLabels implements UseCase<string[]> {
constructor(private readonly datasetsRepository: IDatasetsRepository) {}

/**
* Returns the effective allowed labels. Requires publish permission.
* @param datasetId Numeric dataset ID or persistent identifier.
*/
async execute(datasetId: number | string): Promise<string[]> {
return await this.datasetsRepository.getAllowedDatasetCurationLabels(datasetId)
}
}
15 changes: 15 additions & 0 deletions src/datasets/domain/useCases/curation/GetDatasetCurationStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { UseCase } from '../../../../core/domain/useCases/UseCase'
import { IDatasetsRepository } from '../../repositories/IDatasetsRepository'
import { DatasetCurationStatus } from '../../models/DatasetCuration'

export class GetDatasetCurationStatus implements UseCase<DatasetCurationStatus | null> {
constructor(private readonly datasetsRepository: IDatasetsRepository) {}

/**
* Returns the latest curation record, or null if none exists. Visibility is enforced by the server.
* @param datasetId Numeric dataset ID or persistent identifier.
*/
async execute(datasetId: number | string): Promise<DatasetCurationStatus | null> {
return await this.datasetsRepository.getDatasetCurationStatus(datasetId)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { UseCase } from '../../../../core/domain/useCases/UseCase'
import { IDatasetsRepository } from '../../repositories/IDatasetsRepository'
import { DatasetCurationStatus } from '../../models/DatasetCuration'

export class GetDatasetCurationStatusHistory implements UseCase<DatasetCurationStatus[]> {
constructor(private readonly datasetsRepository: IDatasetsRepository) {}

/**
* Returns curation assignments and removals in server order, newest first.
* @param datasetId Numeric dataset ID or persistent identifier.
*/
async execute(datasetId: number | string): Promise<DatasetCurationStatus[]> {
return await this.datasetsRepository.getDatasetCurationStatusHistory(datasetId)
}
}
14 changes: 14 additions & 0 deletions src/datasets/domain/useCases/curation/SetDatasetCurationStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { UseCase } from '../../../../core/domain/useCases/UseCase'
import { IDatasetsRepository } from '../../repositories/IDatasetsRepository'

export class SetDatasetCurationStatus implements UseCase<void> {
constructor(private readonly datasetsRepository: IDatasetsRepository) {}

/**
* Assigns a configured label to a draft. Requires publish permission; does not change review or publication state.
* @param datasetId Numeric dataset ID or persistent identifier.
*/
async execute(datasetId: number | string, label: string): Promise<void> {
return await this.datasetsRepository.setDatasetCurationStatus(datasetId, label)
}
}
15 changes: 15 additions & 0 deletions src/datasets/domain/useCases/curation/SubmitDatasetForReview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { UseCase } from '../../../../core/domain/useCases/UseCase'
import { IDatasetsRepository } from '../../repositories/IDatasetsRepository'
import { DatasetReviewSubmission } from '../../models/DatasetCuration'

export class SubmitDatasetForReview implements UseCase<DatasetReviewSubmission> {
constructor(private readonly datasetsRepository: IDatasetsRepository) {}

/**
* Submits a draft for review. Requires authentication and edit permission; anonymous submissions are not supported.
* @param datasetId Numeric dataset ID or persistent identifier.
*/
async execute(datasetId: number | string): Promise<DatasetReviewSubmission> {
return await this.datasetsRepository.submitDatasetForReview(datasetId)
}
}
20 changes: 20 additions & 0 deletions src/datasets/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,20 @@ import { GetDatasetStorageDriver } from './domain/useCases/GetDatasetStorageDriv
import { GetDatasetUploadLimits } from './domain/useCases/GetDatasetUploadLimits'
import { GetDatasetReviews } from './domain/useCases/GetDatasetReviews'
import { ExportDatasetMetadata } from './domain/useCases/ExportDatasetMetadata'
import { GetAllowedDatasetCurationLabels } from './domain/useCases/curation/GetAllowedDatasetCurationLabels'
import { GetDatasetCurationStatus } from './domain/useCases/curation/GetDatasetCurationStatus'
import { GetDatasetCurationStatusHistory } from './domain/useCases/curation/GetDatasetCurationStatusHistory'
import { SetDatasetCurationStatus } from './domain/useCases/curation/SetDatasetCurationStatus'
import { DeleteDatasetCurationStatus } from './domain/useCases/curation/DeleteDatasetCurationStatus'
import { SubmitDatasetForReview } from './domain/useCases/curation/SubmitDatasetForReview'

const datasetsRepository = new DatasetsRepository()
const getAllowedDatasetCurationLabels = new GetAllowedDatasetCurationLabels(datasetsRepository)
const getDatasetCurationStatus = new GetDatasetCurationStatus(datasetsRepository)
const getDatasetCurationStatusHistory = new GetDatasetCurationStatusHistory(datasetsRepository)
const setDatasetCurationStatus = new SetDatasetCurationStatus(datasetsRepository)
const deleteDatasetCurationStatus = new DeleteDatasetCurationStatus(datasetsRepository)
const submitDatasetForReview = new SubmitDatasetForReview(datasetsRepository)

const getDataset = new GetDataset(datasetsRepository)
const getDatasetLocks = new GetDatasetLocks(datasetsRepository)
Expand Down Expand Up @@ -93,7 +105,15 @@ const getDatasetUploadLimits = new GetDatasetUploadLimits(datasetsRepository)
const getDatasetReviews = new GetDatasetReviews(datasetsRepository)
const exportDatasetMetadata = new ExportDatasetMetadata(datasetsRepository)

export { DatasetCurationStatus, DatasetReviewSubmission } from './domain/models/DatasetCuration'

export {
getAllowedDatasetCurationLabels,
getDatasetCurationStatus,
getDatasetCurationStatusHistory,
setDatasetCurationStatus,
deleteDatasetCurationStatus,
submitDatasetForReview,
getDataset,
getDatasetLocks,
getDatasetCitation,
Expand Down
55 changes: 55 additions & 0 deletions src/datasets/infra/repositories/DatasetsRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ import { DatasetReview } from '../../domain/models/DatasetReview'
import { transformDatasetReviewsResponseToDatasetReviews } from './transformers/datasetReviewTransformers'
import { ExportedDatasetMetadata } from '../../domain/models/ExportedDatasetMetadata'
import { DatasetPayload } from './transformers/DatasetPayload'
import { DatasetCurationStatus, DatasetReviewSubmission } from '../../domain/models/DatasetCuration'
import {
transformCurationStatus,
transformCurrentCurationStatus,
transformAllowedCurationLabels
} from './transformers/datasetCurationTransformers'

export interface GetAllDatasetPreviewsQueryParams {
per_page?: number
Expand All @@ -48,6 +54,55 @@ export interface GetAllDatasetPreviewsQueryParams {
export class DatasetsRepository extends ApiRepository implements IDatasetsRepository {
private readonly datasetsResourceName: string = 'datasets'

private buildCurationEndpoint(datasetId: number | string, operation: string): string {
return this.buildApiEndpoint(
this.datasetsResourceName,
operation,
typeof datasetId === 'string' ? encodeURIComponent(datasetId) : datasetId
)
}

public async getAllowedDatasetCurationLabels(datasetId: number | string): Promise<string[]> {
const response = await this.doGet(
this.buildCurationEndpoint(datasetId, 'allowedCurationLabels'),
true
)
return transformAllowedCurationLabels(response.data.data)
}

public async getDatasetCurationStatus(
datasetId: number | string
): Promise<DatasetCurationStatus | null> {
const response = await this.doGet(this.buildCurationEndpoint(datasetId, 'curationStatus'), true)
return transformCurrentCurationStatus(response.data.data)
}

public async getDatasetCurationStatusHistory(
datasetId: number | string
): Promise<DatasetCurationStatus[]> {
const response = await this.doGet(
this.buildCurationEndpoint(datasetId, 'curationStatus'),
true,
{ includeHistory: true }
)
return response.data.data.map(transformCurationStatus)
}

public async setDatasetCurationStatus(datasetId: number | string, label: string): Promise<void> {
await this.doPut(this.buildCurationEndpoint(datasetId, 'curationStatus'), '', { label })
}

public async deleteDatasetCurationStatus(datasetId: number | string): Promise<void> {
await this.doDelete(this.buildCurationEndpoint(datasetId, 'curationStatus'))
}

public async submitDatasetForReview(
datasetId: number | string
): Promise<DatasetReviewSubmission> {
const response = await this.doPost(this.buildCurationEndpoint(datasetId, 'submitForReview'), '')
return { inReview: response.data.data.inReview, message: response.data.data.message }
}

public async getDatasetSummaryFieldNames(): Promise<string[]> {
return this.doGet(this.buildApiEndpoint(this.datasetsResourceName, 'summaryFieldNames'))
.then((response) => response.data.data)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { DatasetCurationStatus } from '../../../domain/models/DatasetCuration'

export const transformCurationStatus = (status: DatasetCurationStatus): DatasetCurationStatus => ({
...status
})

export const transformCurrentCurationStatus = (
status: DatasetCurationStatus
): DatasetCurationStatus | null =>
Object.keys(status).length === 0 ? null : transformCurationStatus(status)

export const transformAllowedCurationLabels = (data: { message: string } | string): string[] => {
const labels = typeof data === 'string' ? data : data.message
return labels === '' ? [] : labels.split(',')
}
Loading
Loading