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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ This changelog follows the principles of [Keep a Changelog](https://keepachangel

### Changed

- File pages now include a "Cite Data File" dropdown for downloading file citations in EndNote XML, RIS, and BibTeX formats.
- Hide "Export Metadata" on dataset and file pages that are not for the latest published dataset version.
- Show "Export Metadata" on dataset and file pages for draft version.
- Avoided prop-drilling for file, guestbook, user and external tool repository, so used context to share repository instances.
Expand Down
3 changes: 3 additions & 0 deletions public/locales/en/shared.json
Original file line number Diff line number Diff line change
Expand Up @@ -295,13 +295,16 @@
},
"downloadCitation": {
"title": "Download Citation",
"citeDataset": "Cite Dataset",
"citeDataFile": "Cite Data File",
"viewStyledCitation": "View Styled Citation",
"downloadEndNoteXML": "Download EndNote XML",
"downloadRIS": "Download RIS",
"downloadBibTeX": "Download BibTeX",
"selectCSLStyle": "Select a CSL Style",
"citationInStyle": "Citation in {{styleName}} style",
"downloadError": "An error occurred while downloading the citation",
"downloadSuccess": "Citation downloaded successfully",
"styledCitation": "Styled Citation"
},
"expandableContent": {
Expand Down
3 changes: 3 additions & 0 deletions public/locales/es/shared.json
Original file line number Diff line number Diff line change
Expand Up @@ -295,13 +295,16 @@
},
"downloadCitation": {
"title": "Descargar cita",
"citeDataset": "Citar dataset",
"citeDataFile": "Citar archivo de datos",
"viewStyledCitation": "Ver cita con estilo",
"downloadEndNoteXML": "Descargar EndNote XML",
"downloadRIS": "Descargar RIS",
"downloadBibTeX": "Descargar BibTeX",
"selectCSLStyle": "Seleccionar un estilo CSL",
"citationInStyle": "Cita en estilo {{styleName}}",
"downloadError": "Ocurrió un error al descargar la cita",
"downloadSuccess": "Cita descargada correctamente",
"styledCitation": "Cita con estilo"
},
"expandableContent": {
Expand Down
12 changes: 12 additions & 0 deletions src/files/domain/models/FileCitation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export enum FileCitationFormat {
Internal = 'Internal',
EndNote = 'EndNote',
RIS = 'RIS',
BibTeX = 'BibTeX',
CSLJson = 'CSL'
}

export interface FormattedFileCitation {
content: string
contentType: string
}
5 changes: 5 additions & 0 deletions src/files/domain/repositories/FileRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { FileMetadataDTO } from '@/files/domain/useCases/DTOs/FileMetadataDTO'
import { RestrictFileDTO } from '../useCases/restrictFileDTO'
import { FileVersionSummarySubset } from '../models/FileVersionSummaryInfo'
import { FileVersionPaginationInfo } from '../models/FileVersionPaginationInfo'
import { FileCitationFormat, FormattedFileCitation } from '../models/FileCitation'

export interface FileRepository {
getAllByDatasetPersistentId: (
Expand All @@ -37,6 +38,10 @@ export interface FileRepository {
fileId: number | string,
paginationInfo?: FileVersionPaginationInfo
) => Promise<FileVersionSummarySubset>
getFileCitationByFormat: (
fileId: number | string,
format: FileCitationFormat
) => Promise<FormattedFileCitation>
getById: (id: number, datasetVersionNumber?: string) => Promise<File | undefined>
getMultipleFileDownloadUrl: (ids: number[], downloadMode: FileDownloadMode) => string
getFileDownloadUrl: (id: number, downloadMode: FileDownloadMode) => string
Expand Down
10 changes: 10 additions & 0 deletions src/files/domain/useCases/getFileCitationByFormat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { FormattedFileCitation, FileCitationFormat } from '../models/FileCitation'
import { FileRepository } from '../repositories/FileRepository'

export function getFileCitationByFormat(
fileRepository: FileRepository,
fileId: string | number,
format: FileCitationFormat
): Promise<FormattedFileCitation> {
return fileRepository.getFileCitationByFormat(fileId, format)
}
50 changes: 50 additions & 0 deletions src/files/infrastructure/FileJSDataverseRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
getDatasetFilesTotalDownloadSize,
getFileAndDataset,
getFileCitation,
getFileCitationByFormat as jsGetFileCitationByFormat,
FileCitationFormat as JSFileCitationFormat,
getFileDataTables,
getFileDownloadCount,
getFileUserPermissions,
Expand Down Expand Up @@ -47,6 +49,7 @@ import { FileMetadataDTO } from '@/files/domain/useCases/DTOs/FileMetadataDTO'
import { JSDataverseReadErrorHandler } from '@/shared/helpers/JSDataverseReadErrorHandler'
import { FileVersionSummarySubset } from '../domain/models/FileVersionSummaryInfo'
import { FileVersionPaginationInfo } from '../domain/models/FileVersionPaginationInfo'
import { FileCitationFormat, FormattedFileCitation } from '../domain/models/FileCitation'

const includeDeaccessioned = true

Expand Down Expand Up @@ -269,6 +272,21 @@ export class FileJSDataverseRepository implements FileRepository {
return getFileVersionSummaries.execute(fileId, paginationInfo?.pageSize, paginationInfo?.offset)
}

getFileCitationByFormat(
fileId: number | string,
format: FileCitationFormat
): Promise<FormattedFileCitation> {
return jsGetFileCitationByFormat
.execute(fileId, FileJSDataverseRepository.toJSFileCitationFormat(format))
.then((content) => ({
content,
contentType: FileJSDataverseRepository.getCitationContentType(format)
}))
.catch((error: ReadError) => {
throw new Error(error.message)
})
}

getById(id: number, datasetVersionNumber?: string): Promise<File> {
return FileJSDataverseRepository.getPermissionsByIdOrGuest(id)
.then(({ permissions, isGuestFallback }) => {
Expand Down Expand Up @@ -371,6 +389,38 @@ export class FileJSDataverseRepository implements FileRepository {
})
}

private static getCitationContentType(format: FileCitationFormat): string {
switch (format) {
case FileCitationFormat.EndNote:
return 'application/xml'
case FileCitationFormat.RIS:
return 'application/x-research-info-systems'
case FileCitationFormat.BibTeX:
return 'application/x-bibtex'
case FileCitationFormat.CSLJson:
return 'application/vnd.citationstyles.csl+json'
case FileCitationFormat.Internal:
default:
return 'text/html'
}
}

private static toJSFileCitationFormat(format: FileCitationFormat): JSFileCitationFormat {
switch (format) {
case FileCitationFormat.EndNote:
return JSFileCitationFormat.ENDNOTE
case FileCitationFormat.RIS:
return JSFileCitationFormat.RIS
case FileCitationFormat.BibTeX:
return JSFileCitationFormat.BIBTEX
case FileCitationFormat.CSLJson:
return JSFileCitationFormat.CSL
case FileCitationFormat.Internal:
default:
return JSFileCitationFormat.INTERNAL
}
}

getMultipleFileDownloadUrl(ids: number[], downloadMode: FileDownloadMode): string {
return `/api/access/datafiles/${ids.join(',')}?format=${downloadMode}`
}
Expand Down
7 changes: 6 additions & 1 deletion src/sections/file/File.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,12 @@ export function File({
<Row>
<Col sm={9}>
<span className={styles['citation-title']}>{t('fileCitationTitle')}</span>
<FileCitation citation={file.citation} datasetVersion={file.datasetVersion} />
<FileCitation
citation={file.citation}
datasetVersion={file.datasetVersion}
fileRepository={repository}
fileId={file.id}
/>
<span className={styles['citation-title']}>{t('datasetCitationTitle')}</span>
<DatasetCitation
version={file.datasetVersion}
Expand Down
18 changes: 15 additions & 3 deletions src/sections/file/file-citation/FileCitation.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
import { Col, Row } from '@iqss/dataverse-design-system'
import { Col, Row, Stack } from '@iqss/dataverse-design-system'
import { CitationDescription } from '../../shared/citation/CitationDescription'
import { DatasetPublishingStatus, DatasetVersion } from '../../../dataset/domain/models/Dataset'
import { DatasetCitationTooltip } from '../../dataset/dataset-citation/DatasetCitationTooltip'
import { CitationLearnAbout } from '../../shared/citation/CitationLearnAbout'
import { FileRepository } from '@/files/domain/repositories/FileRepository'
import { FileCitationDownloadButton } from '@/sections/shared/citation/citation-download/FileCitationDownloadButton'
import styles from './FileCitation.module.scss'

interface FileCitationProps {
citation: string
datasetVersion: DatasetVersion
fileRepository: FileRepository
fileId: string | number
}
export function FileCitation({ citation, datasetVersion }: FileCitationProps) {
export function FileCitation({
citation,
datasetVersion,
fileRepository,
fileId
}: FileCitationProps) {
return (
<Row
className={
Expand All @@ -22,7 +31,10 @@ export function FileCitation({ citation, datasetVersion }: FileCitationProps) {
citation={citation}
tooltip={<DatasetCitationTooltip status={datasetVersion.publishingStatus} />}
/>
<CitationLearnAbout />
<Stack direction="horizontal" gap={2} style={{ marginLeft: '-12px' }}>
<FileCitationDownloadButton fileRepository={fileRepository} fileId={fileId} />
<CitationLearnAbout />
</Stack>
</Col>
</Row>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useTranslation } from 'react-i18next'
import { DropdownButton, DropdownButtonItem } from '@iqss/dataverse-design-system'
import { ViewStyledCitationModal } from './ViewStyledCitationModal'
import { useState } from 'react'
import { startTransition, useEffect, useState } from 'react'
import { CitationFormat } from '@/dataset/domain/models/DatasetCitation'
import { useDownloadCitation } from './useDownloadCitation'
import { FormattedCitation } from '@iqss/dataverse-client-javascript/dist/datasets/domain/models/FormattedCitation'
Expand All @@ -26,32 +26,43 @@ export function CitationDownloadButton({ datasetId, version }: CitationDownloadP
version
})

if (error) {
toast.error(t('downloadError'))
}
useEffect(() => {
if (error) {
toast.error(t('downloadError'))
}
}, [error, t])

const handleCloseModal = () => setIsModalOpen(false)
const handleOpenModal = async () => {
setIsModalOpen(true)
startTransition(() => setIsModalOpen(true))
const result = await handleGetCitation(CitationFormat.Internal)
setStyledCitation(result)
}

const handleDownload = (format: CitationFormat, filename: string) => {
void handleDownloadCitation(format, filename).then((downloaded) => {
if (downloaded) {
toast.success(t('downloadSuccess'))
}
})
}

return (
<>
<DropdownButton title="Cite Dataset" id="dataset-actions" variant="link">
<DropdownButton title={t('citeDataset')} id="dataset-actions" variant="link">
<DropdownButtonItem
style={{ textDecoration: 'underline' }}
onClick={() => handleDownloadCitation(CitationFormat.EndNote, `${datasetId}.xml`)}>
onClick={() => handleDownload(CitationFormat.EndNote, `${datasetId}.xml`)}>
{t('downloadEndNoteXML')}
</DropdownButtonItem>
<DropdownButtonItem
style={{ textDecoration: 'underline' }}
onClick={() => handleDownloadCitation(CitationFormat.RIS, `${datasetId}.ris`)}>
onClick={() => handleDownload(CitationFormat.RIS, `${datasetId}.ris`)}>
{t('downloadRIS')}
</DropdownButtonItem>
<DropdownButtonItem
style={{ textDecoration: 'underline' }}
onClick={() => handleDownloadCitation(CitationFormat.BibTeX, `${datasetId}.bib`)}>
onClick={() => handleDownload(CitationFormat.BibTeX, `${datasetId}.bib`)}>
{t('downloadBibTeX')}
</DropdownButtonItem>
<DropdownButtonItem onClick={handleOpenModal} className={styles['styledCitationButton']}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { toast } from 'react-toastify'
import { DropdownButton, DropdownButtonItem } from '@iqss/dataverse-design-system'
import { FileRepository } from '@/files/domain/repositories/FileRepository'
import { FileCitationFormat } from '@/files/domain/models/FileCitation'
import { useDownloadFileCitation } from './useDownloadFileCitation'

interface FileCitationDownloadButtonProps {
fileRepository: FileRepository
fileId: string | number
}

export function FileCitationDownloadButton({
fileRepository,
fileId
}: FileCitationDownloadButtonProps) {
const { t } = useTranslation('shared', { keyPrefix: 'downloadCitation' })

const { error, handleDownloadCitation } = useDownloadFileCitation({
fileRepository,
fileId
})

useEffect(() => {
if (error) {
toast.error(t('downloadError'))
}
}, [error, t])

const handleDownload = (format: FileCitationFormat, filename: string) => {
void handleDownloadCitation(format, filename).then((downloaded) => {
if (downloaded) {
toast.success(t('downloadSuccess'))
}
})
}

return (
<DropdownButton title={t('citeDataFile')} id="file-citation-actions" variant="link">
<DropdownButtonItem
style={{ textDecoration: 'underline' }}
onClick={() => handleDownload(FileCitationFormat.EndNote, `${fileId}.xml`)}>
{t('downloadEndNoteXML')}
</DropdownButtonItem>
<DropdownButtonItem
style={{ textDecoration: 'underline' }}
onClick={() => handleDownload(FileCitationFormat.RIS, `${fileId}.ris`)}>
{t('downloadRIS')}
</DropdownButtonItem>
<DropdownButtonItem
style={{ textDecoration: 'underline' }}
onClick={() => handleDownload(FileCitationFormat.BibTeX, `${fileId}.bib`)}>
{t('downloadBibTeX')}
</DropdownButtonItem>
</DropdownButton>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,16 @@ export function useDownloadCitation({
/**
* Downloads a citation file after fetching it.
*/
const handleDownloadCitation = async (format: CitationFormat, filename: string) => {
const handleDownloadCitation = async (
format: CitationFormat,
filename: string
): Promise<boolean> => {
const citation = await handleGetCitation(format)
if (citation) {
downloadFile(citation.content, filename, citation.contentType)
return true
}
return false
}

return { isLoading, error, handleGetCitation, handleDownloadCitation }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useState } from 'react'
import { FileRepository } from '@/files/domain/repositories/FileRepository'
import { getFileCitationByFormat } from '@/files/domain/useCases/getFileCitationByFormat'
import { FileCitationFormat, FormattedFileCitation } from '@/files/domain/models/FileCitation'
import { downloadFile } from './useDownloadCitation'

export function useDownloadFileCitation({
fileRepository,
fileId
}: {
fileRepository: FileRepository
fileId: string | number
}) {
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string | null>(null)

const handleGetCitation = async (
format: FileCitationFormat
): Promise<FormattedFileCitation | null> => {
setIsLoading(true)
setError(null)
try {
return await getFileCitationByFormat(fileRepository, fileId, format)
} catch (err) {
setError('Failed to fetch citation.')
return null
} finally {
setIsLoading(false)
}
}

const handleDownloadCitation = async (
format: FileCitationFormat,
filename: string
): Promise<boolean> => {
const citation = await handleGetCitation(format)
if (citation) {
downloadFile(citation.content, filename, citation.contentType)
return true
}
return false
}

return { isLoading, error, handleGetCitation, handleDownloadCitation }
}
Loading
Loading