Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2c79b64
feat: add file citation download
ChengShi-1 May 26, 2026
19b8624
update changelog
ChengShi-1 May 27, 2026
e5442a5
Merge branch 'develop' into CiteDataFile-Integration
ChengShi-1 Jun 10, 2026
4eecfeb
update package
ChengShi-1 Jun 10, 2026
1d9a1e4
Merge branch 'develop' into CiteDataFile-Integration
ChengShi-1 Jun 10, 2026
25e1368
feat: add edit guestbook use case, and apply GuestbookDTO to both edi…
ChengShi-1 Jun 23, 2026
5518176
feat: edit guestbook integration
ChengShi-1 Jun 24, 2026
b53808d
feat: guestbook response page
ChengShi-1 Jun 24, 2026
f8853d5
Merge branch 'ManageGuestbook' into 1002-edit-guestbook-and-guestbook…
ChengShi-1 Jun 25, 2026
e0c57c5
fix: increase test coverage
ChengShi-1 Jun 25, 2026
ca340f6
fix: e2e test error
ChengShi-1 Jun 25, 2026
cc8dd2f
fix: make guestbooks enabled by default
ChengShi-1 Jun 26, 2026
260297d
feat: copy guestbook action button
ChengShi-1 Jun 26, 2026
6c547a6
fix: accessibility test error
ChengShi-1 Jun 26, 2026
1839bd6
fix: Lint error
ChengShi-1 Jun 26, 2026
fd320fc
fix: update guestbookDTO
ChengShi-1 Jul 1, 2026
c9e321e
Merge branch 'ManageGuestbook' into 1002-edit-guestbook-and-guestbook…
ChengShi-1 Jul 1, 2026
d62152e
fix: copilot review
ChengShi-1 Jul 1, 2026
97cddaa
Merge with develop branch
ChengShi-1 Jul 2, 2026
e504ed5
Merge branch 'develop' into CiteDataFile-Integration
ChengShi-1 Jul 22, 2026
228cd56
Merge pull request #995 from IQSS/CiteDataFile-Integration
sekmiller Jul 23, 2026
fd0d679
Merge pull request #992 from IQSS/ManageGuestbook
sekmiller Jul 24, 2026
9e82b53
Merge branch 'develop' of https://github.com/IQSS/dataverse-frontend …
ChengShi-1 Jul 30, 2026
d740540
fix: unit test
ChengShi-1 Jul 30, 2026
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
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@ This changelog follows the principles of [Keep a Changelog](https://keepachangel
### Added

- Manage Guestbooks page integration, including:
- a guestbooks table with sorting, enable/disable actions, preview, and per-guestbook response download
- a guestbooks table with sorting, edit, enable/disable actions, preview, and per-guestbook response download
- Create Guestbook and Download All Responses actions
- the Create Guestbook page
- the Create and Edit Guestbook pages
- a checkbox for including guestbooks from parent collections
Comment thread
ChengShi-1 marked this conversation as resolved.
- Dataset Templates UI integration, including create/edit flows, previews, and skeleton states.
- Dataset Page: added a sidebar to show dataset reviews

### 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
2 changes: 1 addition & 1 deletion public/locales/en/datasetTemplates.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"create": "Create Dataset Template",
"makeDefault": "Make Default",
"default": "Default",
"view": "View",
"view": "Preview",
"copy": "Copy",
"edit": "Edit Template",
"delete": "Delete",
Expand Down
34 changes: 32 additions & 2 deletions public/locales/en/guestbooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,24 @@
"enable": "Enable",
"disable": "Disable",
"downloadAllResponses": "Download All Responses",
"view": "View",
"view": "Preview",
"copy": "Copy",
"edit": "Edit",
"delete": "Delete",
"downloadResponses": "Download responses",
"viewResponses": "View Responses"
},
"errors": {
"getGuestbook": "Something went wrong getting the guestbook. Try again later.",
"editGuestbook": "Something went wrong editing the guestbook. Try again later.",
"getGuestbookResponses": "Something went wrong getting guestbook responses. Try again later.",
"toggleEnabled": "Something went wrong updating the guestbook status. Try again later.",
"downloadResponses": "Something went wrong downloading guestbook responses. Try again later."
},
"alerts": {
"statusUpdated": "The guestbook status has been updated.",
"downloadStarted": "Your download has started."
"downloadStarted": "Your download has started.",
"updated": "The guestbook has been updated."
},
"preview": {
"title": "Preview Guestbook",
Expand Down Expand Up @@ -94,5 +98,31 @@
},
"submit": "Create Dataset Guestbook",
"cancel": "Cancel"
},
"edit": {
"title": "Edit Dataset Guestbook"
},
"responses": {
"title": "Guestbook Responses",
"info": "Click \"Download Responses\" to download all collected responses for this guestbook, as a CSV file. To navigate and analyze your collected responses, we recommend importing this CSV file into Excel, Google Sheets or similar software.",
"guestbookName": "Guestbook Name",
"downloadButton": "Download Responses",
"count_one": "{{count}} Response",
"count_other": "{{count}} Responses",
"noRecords": "No records found.",
"table": {
"dataset": "Dataset",
"date": "Date",
"type": "Type",
"file": "File",
"user": "User",
"customQuestions": "Custom Questions"
},
"eventTypes": {
"AccessRequest": "Access Request",
"Download": "Download",
"Subset": "Subset",
"Explore": "Explore"
}
}
}
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
2 changes: 1 addition & 1 deletion public/locales/es/datasetTemplates.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"create": "Crear plantilla de conjunto de datos",
"makeDefault": "Hacer predeterminada",
"default": "Predeterminada",
"view": "Ver",
"view": "Vista previa",
"copy": "Copiar",
"edit": "Editar plantilla",
"delete": "Eliminar"
Expand Down
34 changes: 32 additions & 2 deletions public/locales/es/guestbooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,24 @@
"enable": "Habilitar",
"disable": "Deshabilitar",
"downloadAllResponses": "Descargar todas las respuestas",
"view": "Ver",
"view": "Vista previa",
"copy": "Copiar",
"edit": "Editar",
"delete": "Eliminar",
"downloadResponses": "Descargar respuestas",
"viewResponses": "Ver respuestas"
},
"errors": {
"getGuestbook": "Algo salió mal al obtener el libro de visitas. Inténtalo de nuevo más tarde.",
"editGuestbook": "Algo salió mal al editar el libro de visitas. Inténtalo de nuevo más tarde.",
"getGuestbookResponses": "Algo salió mal al obtener las respuestas del libro de visitas. Inténtalo de nuevo más tarde.",
"toggleEnabled": "Algo salió mal al actualizar el estado del libro de visitas. Inténtalo de nuevo más tarde.",
"downloadResponses": "Algo salió mal al descargar las respuestas del libro de visitas. Inténtalo de nuevo más tarde."
},
"alerts": {
"statusUpdated": "El estado del libro de visitas se ha actualizado.",
"downloadStarted": "La descarga ha comenzado."
"downloadStarted": "La descarga ha comenzado.",
"updated": "El libro de visitas se ha actualizado."
},
"preview": {
"title": "Vista previa del libro de visitas",
Expand Down Expand Up @@ -94,5 +98,31 @@
},
"submit": "Crear libro de visitas del dataset",
"cancel": "Cancelar"
},
"edit": {
"title": "Editar libro de visitas del dataset"
},
"responses": {
"title": "Respuestas del libro de visitas",
"info": "Haz clic en \"Descargar respuestas\" para descargar todas las respuestas recopiladas para este libro de visitas como archivo CSV. Para navegar y analizar las respuestas recopiladas, recomendamos importar este archivo CSV en Excel, Google Sheets o software similar.",
"guestbookName": "Nombre del libro de visitas",
"downloadButton": "Descargar respuestas",
"count_one": "{{count}} respuesta",
"count_other": "{{count}} respuestas",
"noRecords": "No se encontraron registros.",
"table": {
"dataset": "Dataset",
"date": "Fecha",
"type": "Tipo",
"file": "Fichero",
"user": "Usuario",
"customQuestions": "Preguntas personalizadas"
},
"eventTypes": {
"AccessRequest": "Solicitud de acceso",
"Download": "Descarga",
"Subset": "Subconjunto",
"Explore": "Explorar"
}
}
}
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
3 changes: 2 additions & 1 deletion src/guestbooks/domain/models/Guestbook.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
export type GuestbookQuestionType = 'text' | 'textarea' | 'options'

export interface GuestbookOption {
id?: number
value: string
displayOrder: number
}

export interface GuestbookCustomQuestion {
id?: number | string
id?: number
question: string
required: boolean
displayOrder: number
Expand Down
6 changes: 3 additions & 3 deletions src/guestbooks/domain/models/GuestbookResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ export interface GuestbookResponse {
datasetPid: string
date: string
type: EventType
fileName?: string
email?: string
fileId?: number
fileName?: string
filePid?: string
userName: string
email?: string
institution?: string
position?: string
userName: string
customQuestions?: GuestbookResponseCustomQuestion[]
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { PaginationInfo } from '@/shared/pagination/domain/models/PaginationInfo'

export class GuestbookResponsesPaginationInfo extends PaginationInfo<GuestbookResponsesPaginationInfo> {
constructor(page = 1, pageSize = 10, totalItems = 0, itemName = 'Response') {
super(page, pageSize, totalItems, itemName)
}
}
5 changes: 3 additions & 2 deletions src/guestbooks/domain/repositories/GuestbookRepository.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { type CreateGuestbookDTO } from '@iqss/dataverse-client-javascript'
import { GuestbookDTO } from '../useCases/DTOs/GuestbookDTO'
import { Guestbook } from '../models/Guestbook'
import { GuestbookResponseSubset } from '../models/GuestbookResponse'

export interface GuestbookRepository {
createGuestbook: (
collectionIdOrAlias: number | string,
guestbook: CreateGuestbookDTO
guestbook: GuestbookDTO
) => Promise<number>
editGuestbook: (guestbookId: number, guestbook: GuestbookDTO) => Promise<void>
getGuestbook: (guestbookId: number) => Promise<Guestbook>
getGuestbooksByCollectionId: (
collectionIdOrAlias: number | string,
Expand Down
28 changes: 28 additions & 0 deletions src/guestbooks/domain/useCases/DTOs/GuestbookDTO.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { GuestbookQuestionType } from '../../models/Guestbook'

export interface GuestbookDTO {
name: string
enabled: boolean
emailRequired: boolean
nameRequired: boolean
institutionRequired: boolean
positionRequired: boolean
createTime?: string
customQuestions?: GuestbookCustomQuestionDTO[]
}

export interface GuestbookCustomQuestionDTO {
id?: number
question: string
required: boolean
displayOrder: number
type: GuestbookQuestionType
hidden: boolean
optionValues?: GuestbookOptionDTO[]
}

export interface GuestbookOptionDTO {
id?: number
value: string
displayOrder: number
}
Loading
Loading