Skip to content

Commit 4770f56

Browse files
committed
Merge branch 'develop' into 336-file-use-cases-get-available-dataset-file-categories
2 parents 5fd4982 + 158f842 commit 4770f56

File tree

10 files changed

+267
-1
lines changed

10 files changed

+267
-1
lines changed

docs/useCases.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,37 @@ The `datasetId` parameter can be a string, for persistent identifiers, or a numb
567567

568568
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`.
569569

570+
#### Get Dataset Citation In Other Formats
571+
572+
Retrieves the citation for a dataset in a specified bibliographic format.
573+
574+
##### Example call:
575+
576+
```typescript
577+
import { getDatasetCitationInOtherFormats } from '@iqss/dataverse-client-javascript'
578+
579+
/* ... */
580+
581+
const datasetId = 2
582+
const datasetVersionId = '1.0'
583+
584+
getDatasetCitationInOtherFormats
585+
.execute(datasetId, datasetVersionId, format)
586+
.then((citationText: FormattedCitation) => {
587+
/* ... */
588+
})
589+
590+
/* ... */
591+
```
592+
593+
_See [use case](../src/datasets/domain/useCases/GetDatasetCitationInOtherFormats.ts) implementation_.
594+
595+
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).
596+
597+
The `datasetId` parameter can be a string, for persistent identifiers, or a number, for numeric identifiers.
598+
599+
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`.
600+
570601
#### Get Dataset Citation Text By Private URL Token
571602

572603
Returns the Dataset citation text, given an associated Private URL Token.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export enum CitationFormat {
2+
Internal = 'Internal',
3+
EndNote = 'EndNote',
4+
RIS = 'RIS',
5+
BibTeX = 'BibTeX',
6+
CSLJson = 'CSL'
7+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export type FormattedCitation = {
2+
content: string
3+
contentType: string
4+
}

src/datasets/domain/repositories/IDatasetsRepository.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { DatasetVersionDiff } from '../models/DatasetVersionDiff'
1010
import { DatasetDownloadCount } from '../models/DatasetDownloadCount'
1111
import { DatasetVersionSummaryInfo } from '../models/DatasetVersionSummaryInfo'
1212
import { DatasetLinkedCollection } from '../models/DatasetLinkedCollection'
13+
import { CitationFormat } from '../models/CitationFormat'
14+
import { FormattedCitation } from '../models/FormattedCitation'
1315

1416
export interface IDatasetsRepository {
1517
getDataset(
@@ -66,4 +68,10 @@ export interface IDatasetsRepository {
6668
unlinkDataset(datasetId: number, collectionAlias: string): Promise<void>
6769
getDatasetLinkedCollections(datasetId: number | string): Promise<DatasetLinkedCollection[]>
6870
getDatasetAvailableCategories(datasetId: number | string): Promise<string[]>
71+
getDatasetCitationInOtherFormats(
72+
datasetId: number | string,
73+
datasetVersionId: string,
74+
format: CitationFormat,
75+
includeDeaccessioned?: boolean
76+
): Promise<FormattedCitation>
6977
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { UseCase } from '../../../core/domain/useCases/UseCase'
2+
import { IDatasetsRepository } from '../repositories/IDatasetsRepository'
3+
import { DatasetNotNumberedVersion } from '../models/DatasetNotNumberedVersion'
4+
import { FormattedCitation } from '../models/FormattedCitation'
5+
import { CitationFormat } from '../models/CitationFormat'
6+
7+
export class GetDatasetCitationInOtherFormats implements UseCase<FormattedCitation> {
8+
private datasetsRepository: IDatasetsRepository
9+
10+
constructor(datasetsRepository: IDatasetsRepository) {
11+
this.datasetsRepository = datasetsRepository
12+
}
13+
14+
/**
15+
* Returns the dataset citation in the specified format.
16+
*
17+
* @param {number | string} datasetId - The dataset identifier.
18+
* @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.
19+
* @param {CitationFormat} format - The citation format to return. One of: 'EndNote', 'RIS', 'BibTeX', 'CSLJson', 'Internal'.
20+
* @param {boolean} [includeDeaccessioned=false] - Whether to include deaccessioned versions in the search. Defaults to false.
21+
* @returns {Promise<FormattedCitation>} The citation content, format, and content type.
22+
*/
23+
async execute(
24+
datasetId: number | string,
25+
datasetVersionId: string | DatasetNotNumberedVersion = DatasetNotNumberedVersion.LATEST,
26+
format: CitationFormat,
27+
includeDeaccessioned = false
28+
): Promise<FormattedCitation> {
29+
return await this.datasetsRepository.getDatasetCitationInOtherFormats(
30+
datasetId,
31+
datasetVersionId,
32+
format,
33+
includeDeaccessioned
34+
)
35+
}
36+
}

src/datasets/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { LinkDataset } from './domain/useCases/LinkDataset'
2424
import { UnlinkDataset } from './domain/useCases/UnlinkDataset'
2525
import { GetDatasetLinkedCollections } from './domain/useCases/GetDatasetLinkedCollections'
2626
import { GetDatasetAvailableCategories } from './domain/useCases/GetDatasetAvailableCategories'
27+
import { GetDatasetCitationInOtherFormats } from './domain/useCases/GetDatasetCitationInOtherFormats'
2728

2829
const datasetsRepository = new DatasetsRepository()
2930

@@ -62,6 +63,7 @@ const linkDataset = new LinkDataset(datasetsRepository)
6263
const unlinkDataset = new UnlinkDataset(datasetsRepository)
6364
const getDatasetLinkedCollections = new GetDatasetLinkedCollections(datasetsRepository)
6465
const getDatasetAvailableCategories = new GetDatasetAvailableCategories(datasetsRepository)
66+
const getDatasetCitationInOtherFormats = new GetDatasetCitationInOtherFormats(datasetsRepository)
6567

6668
export {
6769
getDataset,
@@ -84,6 +86,7 @@ export {
8486
unlinkDataset,
8587
getDatasetLinkedCollections,
8688
getDatasetAvailableCategories
89+
getDatasetCitationInOtherFormats
8790
}
8891
export { DatasetNotNumberedVersion } from './domain/models/DatasetNotNumberedVersion'
8992
export { DatasetUserPermissions } from './domain/models/DatasetUserPermissions'

src/datasets/infra/repositories/DatasetsRepository.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ import { transformDatasetVersionDiffResponseToDatasetVersionDiff } from './trans
2121
import { DatasetDownloadCount } from '../../domain/models/DatasetDownloadCount'
2222
import { DatasetVersionSummaryInfo } from '../../domain/models/DatasetVersionSummaryInfo'
2323
import { DatasetLinkedCollection } from '../../domain/models/DatasetLinkedCollection'
24+
import { CitationFormat } from '../../domain/models/CitationFormat'
2425
import { transformDatasetLinkedCollectionsResponseToDatasetLinkedCollection } from './transformers/datasetLinkedCollectionsTransformers'
26+
import { FormattedCitation } from '../../domain/models/FormattedCitation'
2527

2628
export interface GetAllDatasetPreviewsQueryParams {
2729
per_page?: number
@@ -76,7 +78,7 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi
7678
}
7779

7880
public async getDatasetCitation(
79-
datasetId: number,
81+
datasetId: number | string,
8082
datasetVersionId: string,
8183
includeDeaccessioned: boolean
8284
): Promise<string> {
@@ -95,6 +97,33 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi
9597
})
9698
}
9799

100+
public async getDatasetCitationInOtherFormats(
101+
datasetId: number | string,
102+
datasetVersionId: string | 'LATEST' = 'LATEST',
103+
format: CitationFormat,
104+
includeDeaccessioned = false
105+
): Promise<FormattedCitation> {
106+
const endpoint = this.buildApiEndpoint(
107+
this.datasetsResourceName,
108+
`versions/${datasetVersionId}/citation/${format}`,
109+
datasetId
110+
)
111+
const response = await this.doGet(endpoint, true, { includeDeaccessioned })
112+
113+
const contentType = response.headers['content-type']
114+
let content: string
115+
if (contentType && contentType.includes('application/json')) {
116+
content = JSON.stringify(response.data)
117+
} else {
118+
content = response.data
119+
}
120+
121+
return {
122+
content,
123+
contentType
124+
}
125+
}
126+
98127
public async getPrivateUrlDatasetCitation(token: string): Promise<string> {
99128
return this.doGet(
100129
this.buildApiEndpoint(this.datasetsResourceName, `privateUrlDatasetVersion/${token}/citation`)

test/integration/datasets/DatasetsRepository.test.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import {
5151
import { FilesRepository } from '../../../src/files/infra/repositories/FilesRepository'
5252
import { DirectUploadClient } from '../../../src/files/infra/clients/DirectUploadClient'
5353
import { createTestFileUploadDestination } from '../../testHelpers/files/fileUploadDestinationHelper'
54+
import { CitationFormat } from '../../../src/datasets/domain/models/CitationFormat'
5455

5556
const TEST_DIFF_DATASET_DTO: DatasetDTO = {
5657
license: {
@@ -492,6 +493,113 @@ describe('DatasetsRepository', () => {
492493
})
493494
})
494495

496+
describe('getDatasetCitationInOtherFormats', () => {
497+
let testDatasetIds: CreatedDatasetIdentifiers
498+
499+
beforeAll(async () => {
500+
testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO)
501+
})
502+
503+
afterAll(async () => {
504+
await deletePublishedDatasetViaApi(testDatasetIds.persistentId)
505+
})
506+
507+
test('should return citation in BibTeX format', async () => {
508+
const citation = await sut.getDatasetCitationInOtherFormats(
509+
testDatasetIds.numericId,
510+
DatasetNotNumberedVersion.LATEST,
511+
CitationFormat.BibTeX
512+
)
513+
514+
expect(typeof citation.content).toBe('string')
515+
expect(citation.contentType).toMatch(/text\/plain/)
516+
})
517+
518+
test('should return citation in BibTeX format using persistent id', async () => {
519+
const citation = await sut.getDatasetCitationInOtherFormats(
520+
testDatasetIds.persistentId,
521+
DatasetNotNumberedVersion.LATEST,
522+
CitationFormat.BibTeX
523+
)
524+
525+
expect(typeof citation.content).toBe('string')
526+
expect(citation.contentType).toMatch(/text\/plain/)
527+
})
528+
529+
test('should return citation in RIS format', async () => {
530+
const citation = await sut.getDatasetCitationInOtherFormats(
531+
testDatasetIds.numericId,
532+
DatasetNotNumberedVersion.LATEST,
533+
CitationFormat.RIS
534+
)
535+
536+
expect(typeof citation.content).toBe('string')
537+
expect(citation.contentType).toMatch(/text\/plain/)
538+
})
539+
540+
test('should return citation in CSLJson format', async () => {
541+
const citation = await sut.getDatasetCitationInOtherFormats(
542+
testDatasetIds.numericId,
543+
DatasetNotNumberedVersion.LATEST,
544+
CitationFormat.CSLJson
545+
)
546+
547+
expect(typeof citation.content).toBe('string')
548+
expect(citation.contentType).toMatch(/application\/json/)
549+
})
550+
551+
test('should return citation in EndNote format', async () => {
552+
const citation = await sut.getDatasetCitationInOtherFormats(
553+
testDatasetIds.numericId,
554+
DatasetNotNumberedVersion.LATEST,
555+
CitationFormat.EndNote
556+
)
557+
558+
expect(typeof citation.content).toBe('string')
559+
expect(citation.contentType).toMatch(/text\/xml/)
560+
})
561+
562+
test('should return citation in Internal format', async () => {
563+
const citation = await sut.getDatasetCitationInOtherFormats(
564+
testDatasetIds.numericId,
565+
DatasetNotNumberedVersion.LATEST,
566+
CitationFormat.Internal
567+
)
568+
569+
expect(typeof citation.content).toBe('string')
570+
expect(citation.contentType).toMatch(/text\/html/)
571+
})
572+
573+
test('should return error when dataset does not exist', async () => {
574+
const nonExistentId = 9999999
575+
const expectedError = new ReadError(`[404] Dataset with ID ${nonExistentId} not found.`)
576+
577+
await expect(
578+
sut.getDatasetCitationInOtherFormats(
579+
nonExistentId,
580+
DatasetNotNumberedVersion.LATEST,
581+
CitationFormat.RIS
582+
)
583+
).rejects.toThrow(expectedError)
584+
})
585+
586+
test('should return citation for deaccessioned dataset when includeDeaccessioned = true', async () => {
587+
await publishDatasetViaApi(testDatasetIds.numericId)
588+
await waitForNoLocks(testDatasetIds.numericId, 10)
589+
await deaccessionDatasetViaApi(testDatasetIds.numericId, '1.0')
590+
591+
const citation = await sut.getDatasetCitationInOtherFormats(
592+
testDatasetIds.numericId,
593+
DatasetNotNumberedVersion.LATEST,
594+
CitationFormat.RIS,
595+
true
596+
)
597+
598+
expect(typeof citation.content).toBe('string')
599+
expect(citation.contentType).toMatch(/text\/plain/)
600+
})
601+
})
602+
495603
describe('getDatasetVersionDiff', () => {
496604
let testDatasetIds: CreatedDatasetIdentifiers
497605

test/testHelpers/roles/roleHelper.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ export const createSuperAdminRoleArray = (): Role[] => {
4343
'ManageFilePermissions',
4444
'PublishDataverse',
4545
'PublishDataset',
46+
'LinkDataverse',
47+
'LinkDataset',
4648
'DeleteDataverse',
4749
'DeleteDatasetDraft'
4850
],
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { GetDatasetCitationInOtherFormats } from '../../../src/datasets/domain/useCases/GetDatasetCitationInOtherFormats'
2+
import { IDatasetsRepository } from '../../../src/datasets/domain/repositories/IDatasetsRepository'
3+
import { ReadError } from '../../../src/core/domain/repositories/ReadError'
4+
import { CitationFormat } from '../../../src/datasets/domain/models/CitationFormat'
5+
import { DatasetNotNumberedVersion } from '../../../src/datasets/domain/models/DatasetNotNumberedVersion'
6+
import { FormattedCitation } from '../../../src/datasets/domain/models/FormattedCitation'
7+
8+
describe('GetDatasetCitationInOtherFormats.execute', () => {
9+
const testDatasetId = 1
10+
const testFormat: CitationFormat = CitationFormat.BibTeX
11+
const testVersion: DatasetNotNumberedVersion = DatasetNotNumberedVersion.LATEST
12+
13+
test('should return citation response on repository success', async () => {
14+
const expectedCitation: FormattedCitation = {
15+
content: '@data{example, ...}',
16+
contentType: 'text/plain'
17+
}
18+
19+
const datasetsRepositoryStub: IDatasetsRepository = {
20+
getDatasetCitationInOtherFormats: jest.fn().mockResolvedValue(expectedCitation)
21+
} as unknown as IDatasetsRepository
22+
23+
const sut = new GetDatasetCitationInOtherFormats(datasetsRepositoryStub)
24+
25+
const actual = await sut.execute(testDatasetId, testVersion, testFormat as CitationFormat)
26+
expect(actual).toEqual(expectedCitation)
27+
})
28+
29+
test('should throw ReadError on repository failure', async () => {
30+
const datasetsRepositoryStub: IDatasetsRepository = {
31+
getDatasetCitationInOtherFormats: jest.fn().mockRejectedValue(new ReadError())
32+
} as unknown as IDatasetsRepository
33+
34+
const sut = new GetDatasetCitationInOtherFormats(datasetsRepositoryStub)
35+
36+
await expect(sut.execute(testDatasetId, testVersion, testFormat)).rejects.toThrow(ReadError)
37+
})
38+
})

0 commit comments

Comments
 (0)