Skip to content

Commit b9604c6

Browse files
committed
feat: update get citation in other format
1 parent 6a025d3 commit b9604c6

File tree

8 files changed

+261
-0
lines changed

8 files changed

+261
-0
lines changed

docs/useCases.md

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

565565
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`.
566566

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

569600
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 CitationFormats {
2+
Internal = 'Internal',
3+
EndNote = 'EndNote',
4+
RIS = 'RIS',
5+
BibTeX = 'BibTeX',
6+
CSLJson = 'CSL'
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { CitationFormats } from './CitationFormats'
2+
3+
export type CitationResponse = {
4+
content: string
5+
format: CitationFormats
6+
contentType: string
7+
}

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 { CitationFormats } from '../models/CitationFormats'
14+
import { CitationResponse } from '../models/CitationResponse'
1315

1416
export interface IDatasetsRepository {
1517
getDataset(
@@ -65,4 +67,10 @@ export interface IDatasetsRepository {
6567
linkDataset(datasetId: number, collectionAlias: string): Promise<void>
6668
unlinkDataset(datasetId: number, collectionAlias: string): Promise<void>
6769
getDatasetLinkedCollections(datasetId: number | string): Promise<DatasetLinkedCollection[]>
70+
getDatasetCitationInOtherFormats(
71+
datasetId: number,
72+
datasetVersionId: string,
73+
format: CitationFormats,
74+
includeDeaccessioned?: boolean
75+
): Promise<CitationResponse>
6876
}
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 { CitationResponse } from '../models/CitationResponse'
5+
import { CitationFormats } from '../models/CitationFormats'
6+
7+
export class GetDatasetCitationInOtherFormats implements UseCase<CitationResponse> {
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} 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 {CitationFormats} 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<CitationResponse>} The citation content, format, and content type.
22+
*/
23+
async execute(
24+
datasetId: number,
25+
datasetVersionId: string | DatasetNotNumberedVersion = DatasetNotNumberedVersion.LATEST,
26+
format: CitationFormats,
27+
includeDeaccessioned = false
28+
): Promise<CitationResponse> {
29+
return await this.datasetsRepository.getDatasetCitationInOtherFormats(
30+
datasetId,
31+
datasetVersionId,
32+
format,
33+
includeDeaccessioned
34+
)
35+
}
36+
}

src/datasets/infra/repositories/DatasetsRepository.ts

Lines changed: 30 additions & 0 deletions
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 { CitationFormats } from '../../domain/models/CitationFormats'
2425
import { transformDatasetLinkedCollectionsResponseToDatasetLinkedCollection } from './transformers/datasetLinkedCollectionsTransformers'
26+
import { CitationResponse } from '../../domain/models/CitationResponse'
2527

2628
export interface GetAllDatasetPreviewsQueryParams {
2729
per_page?: number
@@ -95,6 +97,34 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi
9597
})
9698
}
9799

100+
public async getDatasetCitationInOtherFormats(
101+
datasetId: number,
102+
datasetVersionId: string | 'LATEST' = 'LATEST',
103+
format: CitationFormats,
104+
includeDeaccessioned = false
105+
): Promise<CitationResponse> {
106+
const endpoint = this.buildApiEndpoint(
107+
this.datasetsResourceName,
108+
`versions/${datasetVersionId}/citation/${format}`,
109+
datasetId
110+
)
111+
112+
const queryParams = { includeDeaccessioned }
113+
114+
try {
115+
const response = await this.doGet(endpoint, true, queryParams)
116+
117+
return {
118+
content: response.data,
119+
format,
120+
contentType: response.headers['content-type'] ?? 'text/plain'
121+
}
122+
} catch (error) {
123+
console.error(`[DatasetsRepository] Error fetching citation:`, error)
124+
throw error
125+
}
126+
}
127+
98128
public async getPrivateUrlDatasetCitation(token: string): Promise<string> {
99129
return this.doGet(
100130
this.buildApiEndpoint(this.datasetsResourceName, `privateUrlDatasetVersion/${token}/citation`)

test/integration/datasets/DatasetsRepository.test.ts

Lines changed: 103 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 { CitationFormats } from '../../../src/datasets/domain/models/CitationFormats'
5455

5556
const TEST_DIFF_DATASET_DTO: DatasetDTO = {
5657
license: {
@@ -492,6 +493,108 @@ 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+
CitationFormats.BibTeX
512+
)
513+
514+
expect(typeof citation.content).toBe('string')
515+
expect(citation.format).toBe(CitationFormats.BibTeX)
516+
expect(citation.contentType).toMatch(/text\/plain/)
517+
})
518+
519+
test('should return citation in RIS format', async () => {
520+
const citation = await sut.getDatasetCitationInOtherFormats(
521+
testDatasetIds.numericId,
522+
DatasetNotNumberedVersion.LATEST,
523+
CitationFormats.RIS
524+
)
525+
526+
expect(typeof citation.content).toBe('string')
527+
expect(citation.format).toBe(CitationFormats.RIS)
528+
expect(citation.contentType).toMatch(/text\/plain/)
529+
})
530+
531+
test('should return citation in CSLJson format', async () => {
532+
const citation = await sut.getDatasetCitationInOtherFormats(
533+
testDatasetIds.numericId,
534+
DatasetNotNumberedVersion.LATEST,
535+
CitationFormats.CSLJson
536+
)
537+
538+
expect(typeof citation.content).toBe('object')
539+
expect(citation.format).toBe(CitationFormats.CSLJson)
540+
expect(citation.contentType).toMatch(/application\/json/)
541+
})
542+
543+
test('should return citation in EndNote format', async () => {
544+
const citation = await sut.getDatasetCitationInOtherFormats(
545+
testDatasetIds.numericId,
546+
DatasetNotNumberedVersion.LATEST,
547+
CitationFormats.EndNote
548+
)
549+
550+
expect(typeof citation.content).toBe('string')
551+
expect(citation.format).toBe(CitationFormats.EndNote)
552+
expect(citation.contentType).toMatch('text/xml;charset=UTF-8')
553+
})
554+
555+
test('should return citation in Internal format', async () => {
556+
const citation = await sut.getDatasetCitationInOtherFormats(
557+
testDatasetIds.numericId,
558+
DatasetNotNumberedVersion.LATEST,
559+
CitationFormats.Internal
560+
)
561+
562+
expect(typeof citation.content).toBe('string')
563+
expect(citation.format).toBe(CitationFormats.Internal)
564+
expect(citation.contentType).toMatch(/text\/html/)
565+
})
566+
567+
test('should return error when dataset does not exist', async () => {
568+
const nonExistentId = 9999999
569+
const expectedError = new ReadError(`[404] Dataset with ID ${nonExistentId} not found.`)
570+
571+
await expect(
572+
sut.getDatasetCitationInOtherFormats(
573+
nonExistentId,
574+
DatasetNotNumberedVersion.LATEST,
575+
CitationFormats.RIS
576+
)
577+
).rejects.toThrow(expectedError)
578+
})
579+
580+
test('should return citation for deaccessioned dataset when includeDeaccessioned = true', async () => {
581+
await publishDatasetViaApi(testDatasetIds.numericId)
582+
await waitForNoLocks(testDatasetIds.numericId, 10)
583+
await deaccessionDatasetViaApi(testDatasetIds.numericId, '1.0')
584+
585+
const citation = await sut.getDatasetCitationInOtherFormats(
586+
testDatasetIds.numericId,
587+
DatasetNotNumberedVersion.LATEST,
588+
CitationFormats.RIS,
589+
true
590+
)
591+
592+
expect(typeof citation.content).toBe('string')
593+
expect(citation.format).toBe(CitationFormats.RIS)
594+
expect(citation.contentType).toMatch(/text\/plain/)
595+
})
596+
})
597+
495598
describe('getDatasetVersionDiff', () => {
496599
let testDatasetIds: CreatedDatasetIdentifiers
497600

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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 { CitationFormats } from '../../../src/datasets/domain/models/CitationFormats'
5+
import { DatasetNotNumberedVersion } from '../../../src/datasets/domain/models/DatasetNotNumberedVersion'
6+
import { CitationResponse } from '../../../src/datasets/domain/models/CitationResponse'
7+
8+
describe('GetDatasetCitationInOtherFormats.execute', () => {
9+
const testDatasetId = 1
10+
const testFormat: CitationFormats = CitationFormats.BibTeX
11+
const testVersion: DatasetNotNumberedVersion = DatasetNotNumberedVersion.LATEST
12+
13+
test('should return citation response on repository success', async () => {
14+
const expectedCitation: CitationResponse = {
15+
content: '@data{example, ...}',
16+
format: CitationFormats.BibTeX,
17+
contentType: 'text/plain'
18+
}
19+
20+
const datasetsRepositoryStub: IDatasetsRepository = {
21+
getDatasetCitationInOtherFormats: jest.fn().mockResolvedValue(expectedCitation)
22+
} as unknown as IDatasetsRepository
23+
24+
const sut = new GetDatasetCitationInOtherFormats(datasetsRepositoryStub)
25+
26+
const actual = await sut.execute(testDatasetId, testVersion, testFormat as CitationFormats)
27+
expect(actual).toEqual(expectedCitation)
28+
})
29+
30+
test('should throw ReadError on repository failure', async () => {
31+
const datasetsRepositoryStub: IDatasetsRepository = {
32+
getDatasetCitationInOtherFormats: jest.fn().mockRejectedValue(new ReadError())
33+
} as unknown as IDatasetsRepository
34+
35+
const sut = new GetDatasetCitationInOtherFormats(datasetsRepositoryStub)
36+
37+
await expect(sut.execute(testDatasetId, testVersion, testFormat)).rejects.toThrow(ReadError)
38+
})
39+
})

0 commit comments

Comments
 (0)