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
31 changes: 31 additions & 0 deletions docs/useCases.md
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,37 @@ The `datasetId` parameter can be a string, for persistent identifiers, or a numb

There is an optional third parameter called `includeDeaccessioned`, which indicates whether to consider deaccessioned versions or not in the dataset search. If not set, the default value is `false`.

#### Get Dataset Citation In Other Formats

Retrieves the citation for a dataset in a specified bibliographic format.

##### Example call:

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

/* ... */

const datasetId = 2
const datasetVersionId = '1.0'

getDatasetCitationInOtherFormats
.execute(datasetId, datasetVersionId, format)
.then((citationText: FormattedCitation) => {
/* ... */
})

/* ... */
```

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

Supported formats include 'EndNote' (XML), 'RIS' (plain text), 'BibTeX' (plain text), 'CSLJson' (JSON), and 'Internal' (HTML). The response contains the raw citation content in the requested format, the format type, and the content type (MIME type).

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

There is an optional third parameter called `includeDeaccessioned`, which indicates whether to consider deaccessioned versions or not in the dataset search. If not set, the default value is `false`.

#### Get Dataset Citation Text By Private URL Token

Returns the Dataset citation text, given an associated Private URL Token.
Expand Down
7 changes: 7 additions & 0 deletions src/datasets/domain/models/CitationFormat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export enum CitationFormat {
Internal = 'Internal',
EndNote = 'EndNote',
RIS = 'RIS',
BibTeX = 'BibTeX',
CSLJson = 'CSL'
}
4 changes: 4 additions & 0 deletions src/datasets/domain/models/FormattedCitation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type FormattedCitation = {
content: string
contentType: 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 @@ -10,6 +10,8 @@ import { DatasetVersionDiff } from '../models/DatasetVersionDiff'
import { DatasetDownloadCount } from '../models/DatasetDownloadCount'
import { DatasetVersionSummaryInfo } from '../models/DatasetVersionSummaryInfo'
import { DatasetLinkedCollection } from '../models/DatasetLinkedCollection'
import { CitationFormat } from '../models/CitationFormat'
import { FormattedCitation } from '../models/FormattedCitation'

export interface IDatasetsRepository {
getDataset(
Expand Down Expand Up @@ -65,4 +67,10 @@ export interface IDatasetsRepository {
linkDataset(datasetId: number, collectionAlias: string): Promise<void>
unlinkDataset(datasetId: number, collectionAlias: string): Promise<void>
getDatasetLinkedCollections(datasetId: number | string): Promise<DatasetLinkedCollection[]>
getDatasetCitationInOtherFormats(
datasetId: number | string,
datasetVersionId: string,
format: CitationFormat,
includeDeaccessioned?: boolean
): Promise<FormattedCitation>
}
36 changes: 36 additions & 0 deletions src/datasets/domain/useCases/GetDatasetCitationInOtherFormats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { UseCase } from '../../../core/domain/useCases/UseCase'
import { IDatasetsRepository } from '../repositories/IDatasetsRepository'
import { DatasetNotNumberedVersion } from '../models/DatasetNotNumberedVersion'
import { FormattedCitation } from '../models/FormattedCitation'
import { CitationFormat } from '../models/CitationFormat'

export class GetDatasetCitationInOtherFormats implements UseCase<FormattedCitation> {
private datasetsRepository: IDatasetsRepository

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

/**
* Returns the dataset citation in the specified format.
*
* @param {number | string} datasetId - The dataset identifier.
* @param {string | DatasetNotNumberedVersion} [datasetVersionId=DatasetNotNumberedVersion.LATEST] - The dataset version identifier, which can be a version-specific string (e.g., '1.0') or a DatasetNotNumberedVersion enum value. Defaults to LATEST.
* @param {CitationFormat} format - The citation format to return. One of: 'EndNote', 'RIS', 'BibTeX', 'CSLJson', 'Internal'.
* @param {boolean} [includeDeaccessioned=false] - Whether to include deaccessioned versions in the search. Defaults to false.
* @returns {Promise<FormattedCitation>} The citation content, format, and content type.
*/
async execute(
datasetId: number | string,
datasetVersionId: string | DatasetNotNumberedVersion = DatasetNotNumberedVersion.LATEST,
format: CitationFormat,
includeDeaccessioned = false
): Promise<FormattedCitation> {
return await this.datasetsRepository.getDatasetCitationInOtherFormats(
datasetId,
datasetVersionId,
format,
includeDeaccessioned
)
}
}
5 changes: 4 additions & 1 deletion src/datasets/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { DeleteDatasetDraft } from './domain/useCases/DeleteDatasetDraft'
import { LinkDataset } from './domain/useCases/LinkDataset'
import { UnlinkDataset } from './domain/useCases/UnlinkDataset'
import { GetDatasetLinkedCollections } from './domain/useCases/GetDatasetLinkedCollections'
import { GetDatasetCitationInOtherFormats } from './domain/useCases/GetDatasetCitationInOtherFormats'

const datasetsRepository = new DatasetsRepository()

Expand Down Expand Up @@ -60,6 +61,7 @@ const deleteDatasetDraft = new DeleteDatasetDraft(datasetsRepository)
const linkDataset = new LinkDataset(datasetsRepository)
const unlinkDataset = new UnlinkDataset(datasetsRepository)
const getDatasetLinkedCollections = new GetDatasetLinkedCollections(datasetsRepository)
const getDatasetCitationInOtherFormats = new GetDatasetCitationInOtherFormats(datasetsRepository)

export {
getDataset,
Expand All @@ -80,7 +82,8 @@ export {
deleteDatasetDraft,
linkDataset,
unlinkDataset,
getDatasetLinkedCollections
getDatasetLinkedCollections,
getDatasetCitationInOtherFormats
}
export { DatasetNotNumberedVersion } from './domain/models/DatasetNotNumberedVersion'
export { DatasetUserPermissions } from './domain/models/DatasetUserPermissions'
Expand Down
31 changes: 30 additions & 1 deletion src/datasets/infra/repositories/DatasetsRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import { transformDatasetVersionDiffResponseToDatasetVersionDiff } from './trans
import { DatasetDownloadCount } from '../../domain/models/DatasetDownloadCount'
import { DatasetVersionSummaryInfo } from '../../domain/models/DatasetVersionSummaryInfo'
import { DatasetLinkedCollection } from '../../domain/models/DatasetLinkedCollection'
import { CitationFormat } from '../../domain/models/CitationFormat'
import { transformDatasetLinkedCollectionsResponseToDatasetLinkedCollection } from './transformers/datasetLinkedCollectionsTransformers'
import { FormattedCitation } from '../../domain/models/FormattedCitation'

export interface GetAllDatasetPreviewsQueryParams {
per_page?: number
Expand Down Expand Up @@ -76,7 +78,7 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi
}

public async getDatasetCitation(
datasetId: number,
datasetId: number | string,
datasetVersionId: string,
includeDeaccessioned: boolean
): Promise<string> {
Expand All @@ -95,6 +97,33 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi
})
}

public async getDatasetCitationInOtherFormats(
datasetId: number | string,
datasetVersionId: string | 'LATEST' = 'LATEST',
format: CitationFormat,
includeDeaccessioned = false
): Promise<FormattedCitation> {
const endpoint = this.buildApiEndpoint(
this.datasetsResourceName,
`versions/${datasetVersionId}/citation/${format}`,
datasetId
)
const response = await this.doGet(endpoint, true, { includeDeaccessioned })

const contentType = response.headers['content-type']
let content: string
if (contentType && contentType.includes('application/json')) {
content = JSON.stringify(response.data)
} else {
content = response.data
}

return {
content,
contentType
}
}

public async getPrivateUrlDatasetCitation(token: string): Promise<string> {
return this.doGet(
this.buildApiEndpoint(this.datasetsResourceName, `privateUrlDatasetVersion/${token}/citation`)
Expand Down
108 changes: 108 additions & 0 deletions test/integration/datasets/DatasetsRepository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import { FilesRepository } from '../../../src/files/infra/repositories/FilesRepository'
import { DirectUploadClient } from '../../../src/files/infra/clients/DirectUploadClient'
import { createTestFileUploadDestination } from '../../testHelpers/files/fileUploadDestinationHelper'
import { CitationFormat } from '../../../src/datasets/domain/models/CitationFormat'

const TEST_DIFF_DATASET_DTO: DatasetDTO = {
license: {
Expand Down Expand Up @@ -492,6 +493,113 @@
})
})

describe('getDatasetCitationInOtherFormats', () => {
let testDatasetIds: CreatedDatasetIdentifiers

beforeAll(async () => {
testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO)
})

afterAll(async () => {
await deletePublishedDatasetViaApi(testDatasetIds.persistentId)
})

test('should return citation in BibTeX format', async () => {
const citation = await sut.getDatasetCitationInOtherFormats(
testDatasetIds.numericId,
DatasetNotNumberedVersion.LATEST,
CitationFormat.BibTeX
)

expect(typeof citation.content).toBe('string')
expect(citation.contentType).toMatch(/text\/plain/)
})

test('should return citation in BibTeX format using persistent id', async () => {
const citation = await sut.getDatasetCitationInOtherFormats(
testDatasetIds.persistentId,
DatasetNotNumberedVersion.LATEST,
CitationFormat.BibTeX
)

expect(typeof citation.content).toBe('string')
expect(citation.contentType).toMatch(/text\/plain/)
})

test('should return citation in RIS format', async () => {
const citation = await sut.getDatasetCitationInOtherFormats(
testDatasetIds.numericId,
DatasetNotNumberedVersion.LATEST,
CitationFormat.RIS
)

expect(typeof citation.content).toBe('string')
expect(citation.contentType).toMatch(/text\/plain/)
})

test('should return citation in CSLJson format', async () => {
const citation = await sut.getDatasetCitationInOtherFormats(
testDatasetIds.numericId,
DatasetNotNumberedVersion.LATEST,
CitationFormat.CSLJson
)

expect(typeof citation.content).toBe('string')
expect(citation.contentType).toMatch(/application\/json/)
})

test('should return citation in EndNote format', async () => {
const citation = await sut.getDatasetCitationInOtherFormats(
testDatasetIds.numericId,
DatasetNotNumberedVersion.LATEST,
CitationFormat.EndNote
)

expect(typeof citation.content).toBe('string')
expect(citation.contentType).toMatch(/text\/xml/)
})

test('should return citation in Internal format', async () => {
const citation = await sut.getDatasetCitationInOtherFormats(
testDatasetIds.numericId,
DatasetNotNumberedVersion.LATEST,
CitationFormat.Internal
)

expect(typeof citation.content).toBe('string')
expect(citation.contentType).toMatch(/text\/html/)
})

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

await expect(
sut.getDatasetCitationInOtherFormats(
nonExistentId,
DatasetNotNumberedVersion.LATEST,
CitationFormat.RIS
)
).rejects.toThrow(expectedError)
})

test('should return citation for deaccessioned dataset when includeDeaccessioned = true', async () => {
await publishDatasetViaApi(testDatasetIds.numericId)
await waitForNoLocks(testDatasetIds.numericId, 10)
await deaccessionDatasetViaApi(testDatasetIds.numericId, '1.0')

const citation = await sut.getDatasetCitationInOtherFormats(
testDatasetIds.numericId,
DatasetNotNumberedVersion.LATEST,
CitationFormat.RIS,
true
)

expect(typeof citation.content).toBe('string')
expect(citation.contentType).toMatch(/text\/plain/)
})
})

describe('getDatasetVersionDiff', () => {
let testDatasetIds: CreatedDatasetIdentifiers

Expand Down Expand Up @@ -955,7 +1063,7 @@
])
})
// TODO: add this test when https://github.com/IQSS/dataverse-client-javascript/issues/343 is fixed
test.skip('should throw error if trying to update an outdated internal version dataset', async () => {

Check warning on line 1066 in test/integration/datasets/DatasetsRepository.test.ts

View workflow job for this annotation

GitHub Actions / lint

Disabled test

Check warning on line 1066 in test/integration/datasets/DatasetsRepository.test.ts

View workflow job for this annotation

GitHub Actions / lint

Disabled test
const testDataset = {
metadataBlockValues: [
{
Expand Down
5 changes: 1 addition & 4 deletions test/testHelpers/roles/roleHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,7 @@ export const createSuperAdminRoleArray = (): Role[] => {
'ManageDatasetPermissions',
'ManageFilePermissions',
'PublishDataverse',
'LinkDataverse',
'PublishDataset',
'LinkDataset',
'DeleteDataverse',
'DeleteDatasetDraft'
],
Expand Down Expand Up @@ -101,11 +99,10 @@ export const createSuperAdminRoleArray = (): Role[] => {
'ManageDatasetPermissions',
'ManageFilePermissions',
'PublishDataset',
'LinkDataset',
'DeleteDatasetDraft'
],
description:
'For datasets, a person who can edit License + Terms, edit Permissions, and publish and link datasets.',
'For datasets, a person who can edit License + Terms, edit Permissions, and publish datasets.',
id: 7
},
{
Expand Down
38 changes: 38 additions & 0 deletions test/unit/datasets/GetDatasetCitationInOtherFormats.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { GetDatasetCitationInOtherFormats } from '../../../src/datasets/domain/useCases/GetDatasetCitationInOtherFormats'
import { IDatasetsRepository } from '../../../src/datasets/domain/repositories/IDatasetsRepository'
import { ReadError } from '../../../src/core/domain/repositories/ReadError'
import { CitationFormat } from '../../../src/datasets/domain/models/CitationFormat'
import { DatasetNotNumberedVersion } from '../../../src/datasets/domain/models/DatasetNotNumberedVersion'
import { FormattedCitation } from '../../../src/datasets/domain/models/FormattedCitation'

describe('GetDatasetCitationInOtherFormats.execute', () => {
const testDatasetId = 1
const testFormat: CitationFormat = CitationFormat.BibTeX
const testVersion: DatasetNotNumberedVersion = DatasetNotNumberedVersion.LATEST

test('should return citation response on repository success', async () => {
const expectedCitation: FormattedCitation = {
content: '@data{example, ...}',
contentType: 'text/plain'
}

const datasetsRepositoryStub: IDatasetsRepository = {
getDatasetCitationInOtherFormats: jest.fn().mockResolvedValue(expectedCitation)
} as unknown as IDatasetsRepository

const sut = new GetDatasetCitationInOtherFormats(datasetsRepositoryStub)

const actual = await sut.execute(testDatasetId, testVersion, testFormat as CitationFormat)
expect(actual).toEqual(expectedCitation)
})

test('should throw ReadError on repository failure', async () => {
const datasetsRepositoryStub: IDatasetsRepository = {
getDatasetCitationInOtherFormats: jest.fn().mockRejectedValue(new ReadError())
} as unknown as IDatasetsRepository

const sut = new GetDatasetCitationInOtherFormats(datasetsRepositoryStub)

await expect(sut.execute(testDatasetId, testVersion, testFormat)).rejects.toThrow(ReadError)
})
})