From 01199b210d983a8de3b2e56bcace1eaf14a66ba9 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Mon, 13 Apr 2026 14:59:08 -0400 Subject: [PATCH 1/8] feat: show terms modal if no default license --- CHANGELOG.md | 1 + .../access-dataset-menu/AccessDatasetMenu.tsx | 37 ++-- .../download-files/DownloadFilesButton.tsx | 64 +++---- ...oadWithTermsAndGuestbookModal.module.scss} | 0 ...=> DownloadWithTermsAndGuestbookModal.tsx} | 15 +- .../GuestbookCollectForm.tsx | 77 ++++---- .../useGuestbookCollectSubmission.ts | 6 +- .../access-file-menu/FileDownloadOptions.tsx | 34 ++-- .../FileNonTabularDownloadOptions.tsx | 6 +- .../FileTabularDownloadOptions.tsx | 6 +- ...oadWithTermsAndGuestbookModal.stories.tsx} | 10 +- .../AccessDatasetMenu.spec.tsx | 172 +++++++++++++++++- .../DownloadFilesButton.spec.tsx | 102 +++++++++++ ...wnloadWithTermsAndGuestbookModal.spec.tsx} | 124 ++++++++++--- .../access-file-menu/AccessFileMenu.spec.tsx | 141 ++++++++++++++ .../FileNonTabularDownloadOptions.spec.tsx | 12 +- .../FileTabularDownloadOptions.spec.tsx | 14 +- .../e2e/sections/dataset/Dataset.spec.tsx | 157 ++++++++++++---- .../e2e/sections/file/File.spec.tsx | 93 +++++++++- .../shared/datasets/DatasetHelper.ts | 16 ++ 20 files changed, 877 insertions(+), 210 deletions(-) rename src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/{DownloadWithGuestbookModal.module.scss => DownloadWithTermsAndGuestbookModal.module.scss} (100%) rename src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/{DownloadWithGuestbookModal.tsx => DownloadWithTermsAndGuestbookModal.tsx} (96%) rename src/stories/guestbooks/guestbook-applied-modal/{DownloadWithGuestbookModal.stories.tsx => DownloadWithTermsAndGuestbookModal.stories.tsx} (83%) rename tests/component/sections/dataset/dataset-files/guestbook/{DownloadWithGuestbookModal.spec.tsx => DownloadWithTermsAndGuestbookModal.spec.tsx} (87%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4082a6953..7921e679f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ This changelog follows the principles of [Keep a Changelog](https://keepachangel - Added runtime configuration options for homepage branding and support link. - Added an environment variable to docker-compose-dev.yml to hide the OIDC client used in the SPA from the JSF frontend: DATAVERSE_AUTH_OIDC_HIDDEN_JSF: 1 - Download with terms of use and guestbook. +- Show terms modal before download when dataset has custom terms, a non-default license (not CC0 1.0), or a guestbook. Draft datasets and dataset editors bypass the modal. ### Changed diff --git a/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx b/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx index fda7c18bd..74d1ae2cf 100644 --- a/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx +++ b/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx @@ -8,12 +8,13 @@ import { DatasetLicense, DatasetPermissions, DatasetPublishingStatus, - DatasetVersion + DatasetVersion, + defaultLicense } from '../../../../dataset/domain/models/Dataset' import { FileDownloadSize, FileDownloadMode } from '../../../../files/domain/models/FileMetadata' import { DatasetExploreOptions } from '../DatasetToolsOptions' import { useAccessRepository } from '@/sections/access/AccessRepositoryContext' -import { DownloadWithGuestbookModal } from '@/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/DownloadWithGuestbookModal' +import { DownloadWithTermsAndGuestbookModal } from '@/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/DownloadWithTermsAndGuestbookModal' import { downloadFromSignedUrl, EMPTY_GUESTBOOK_RESPONSE, @@ -47,14 +48,18 @@ export function AccessDatasetMenu({ customTerms }: AccessDatasetMenuProps) { const { t } = useTranslation('dataset') - const [showDownloadWithGuestbookModal, setShowDownloadWithGuestbookModal] = useState(false) + const [showDownloadWithTermsAndGuestbookModal, setShowDownloadWithTermsAndGuestbookModal] = + useState(false) const [selectedDownloadFormat, setSelectedDownloadFormat] = useState( FileDownloadMode.ORIGINAL ) - const hasGuestbook = - guestbookId !== undefined && - version.publishingStatus !== DatasetPublishingStatus.DRAFT && - !permissions.canUpdateDataset + const isDraft = version.publishingStatus === DatasetPublishingStatus.DRAFT + const bypassTermsGuard = isDraft || permissions.canUpdateDataset + const hasGuestbook = guestbookId !== undefined + const hasNonDefaultLicense = license !== undefined && license.name !== defaultLicense.name + const hasCustomTerms = customTerms !== undefined + const shouldShowModal = + !bypassTermsGuard && (hasGuestbook || hasCustomTerms || hasNonDefaultLicense) const flesToDownloadSizeIsZero = fileDownloadSizes.map(({ value }) => value).reduce((acc, curr) => acc + curr, 0) === 0 @@ -79,7 +84,7 @@ export function AccessDatasetMenu({ ) => { event.preventDefault() setSelectedDownloadFormat(mode) - setShowDownloadWithGuestbookModal(true) + setShowDownloadWithTermsAndGuestbookModal(true) } return ( @@ -96,15 +101,15 @@ export function AccessDatasetMenu({ datasetNumericId={datasetNumericId} hasOneTabularFileAtLeast={hasOneTabularFileAtLeast} fileDownloadSizes={fileDownloadSizes} - hasGuestbook={hasGuestbook} + requiresTermsOrGuestbook={shouldShowModal} onDownloadWithGuestbook={handleDownloadWithGuestbook} /> - {hasGuestbook && showDownloadWithGuestbookModal && ( - setShowDownloadWithGuestbookModal(false)} + {shouldShowModal && showDownloadWithTermsAndGuestbookModal && ( + setShowDownloadWithTermsAndGuestbookModal(false)} datasetId={datasetNumericId} // TODO: we should allow this to pass persistentId when we have the backend support for guestbook submission with persistentId datasetPersistentId={persistentId} guestbookId={guestbookId} @@ -121,7 +126,7 @@ interface DatasetDownloadOptionsProps { datasetNumericId?: number | string hasOneTabularFileAtLeast: boolean fileDownloadSizes: FileDownloadSize[] - hasGuestbook: boolean + requiresTermsOrGuestbook: boolean onDownloadWithGuestbook: (event: React.MouseEvent, mode: FileDownloadMode) => void } @@ -129,7 +134,7 @@ const DatasetDownloadOptions = ({ datasetNumericId, hasOneTabularFileAtLeast, fileDownloadSizes, - hasGuestbook, + requiresTermsOrGuestbook, onDownloadWithGuestbook }: DatasetDownloadOptionsProps) => { const { t } = useTranslation('dataset') @@ -140,7 +145,7 @@ const DatasetDownloadOptions = ({ event: React.MouseEvent, mode: FileDownloadMode ): void => { - if (hasGuestbook) { + if (requiresTermsOrGuestbook) { onDownloadWithGuestbook(event, mode) return } diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.tsx index 7283a0ef2..96336e6c4 100644 --- a/src/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.tsx @@ -3,14 +3,14 @@ import { useTranslation } from 'react-i18next' import { Button, DropdownButton, DropdownButtonItem } from '@iqss/dataverse-design-system' import { MouseEvent, useState } from 'react' import { toast } from 'react-toastify' -import { DatasetPublishingStatus } from '@/dataset/domain/models/Dataset' +import { DatasetPublishingStatus, defaultLicense } from '@/dataset/domain/models/Dataset' import { FileDownloadMode } from '../../../../../../files/domain/models/FileMetadata' import { useDataset } from '../../../../DatasetContext' import { FileSelection } from '../../row-selection/useFileSelection' import { NoSelectedFilesModal } from '../no-selected-files-modal/NoSelectedFilesModal' import { FilePreview } from '../../../../../../files/domain/models/FilePreview' import { useMediaQuery } from '../../../../../../shared/hooks/useMediaQuery' -import { DownloadWithGuestbookModal } from '../file-actions-cell/file-action-buttons/file-options-menu/DownloadWithGuestbookModal' +import { DownloadWithTermsAndGuestbookModal } from '../file-actions-cell/file-action-buttons/file-options-menu/DownloadWithTermsAndGuestbookModal' import { downloadFromSignedUrl, EMPTY_GUESTBOOK_RESPONSE, @@ -31,7 +31,8 @@ export function DownloadFilesButton({ files, fileSelection }: DownloadFilesButto const { t } = useTranslation('files') const { dataset } = useDataset() const [showNoFilesSelectedModal, setShowNoFilesSelectedModal] = useState(false) - const [showDownloadWithGuestbookModal, setShowDownloadWithGuestbookModal] = useState(false) + const [showDownloadWithTermsAndGuestbookModal, setShowDownloadWithTermsAndGuestbookModal] = + useState(false) const [selectedDownloadFormat, setSelectedDownloadFormat] = useState( FileDownloadMode.ORIGINAL ) @@ -43,10 +44,15 @@ export function DownloadFilesButton({ files, fileSelection }: DownloadFilesButto const fileIdsForGuestbookSubmission = allFilesSelected ? undefined : getFileIdsFromSelection(fileSelection) - const hasGuestbook = - dataset?.guestbookId !== undefined && - dataset?.version.publishingStatus !== DatasetPublishingStatus.DRAFT && - !dataset?.permissions.canUpdateDataset + const isDraftDataset = dataset?.version.publishingStatus === DatasetPublishingStatus.DRAFT + const canEdit = dataset?.permissions.canUpdateDataset ?? false + const bypassTermsGuard = isDraftDataset || canEdit + const hasGuestbook = dataset?.guestbookId !== undefined + const hasNonDefaultLicense = + dataset?.license !== undefined && dataset.license.name !== defaultLicense.name + const hasCustomTerms = dataset?.termsOfUse?.customTerms !== undefined + const shouldShowModal = + !bypassTermsGuard && (hasGuestbook || hasCustomTerms || hasNonDefaultLicense) const onClick = (event: MouseEvent, downloadMode: FileDownloadMode) => { if (fileSelectionCount === SELECTED_FILES_EMPTY) { @@ -55,10 +61,10 @@ export function DownloadFilesButton({ files, fileSelection }: DownloadFilesButto return } - if (hasGuestbook) { + if (shouldShowModal) { event.preventDefault() setSelectedDownloadFormat(downloadMode) - setShowDownloadWithGuestbookModal(true) + setShowDownloadWithTermsAndGuestbookModal(true) return } @@ -101,15 +107,17 @@ export function DownloadFilesButton({ files, fileSelection }: DownloadFilesButto show={showNoFilesSelectedModal} handleClose={() => setShowNoFilesSelectedModal(false)} /> - {hasGuestbook && showDownloadWithGuestbookModal && ( - setShowDownloadWithGuestbookModal(false)} + datasetLicense={dataset.license} + datasetCustomTerms={dataset.termsOfUse?.customTerms} + show={showDownloadWithTermsAndGuestbookModal} + handleClose={() => setShowDownloadWithTermsAndGuestbookModal(false)} /> )} @@ -140,27 +148,15 @@ export function DownloadFilesButton({ files, fileSelection }: DownloadFilesButto // no tabular file content return ( <> - {hasGuestbook ? ( - - ) : ( - - )} + {downloadFeedbackModals} diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/DownloadWithGuestbookModal.module.scss b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/DownloadWithTermsAndGuestbookModal.module.scss similarity index 100% rename from src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/DownloadWithGuestbookModal.module.scss rename to src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/DownloadWithTermsAndGuestbookModal.module.scss diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/DownloadWithGuestbookModal.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/DownloadWithTermsAndGuestbookModal.tsx similarity index 96% rename from src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/DownloadWithGuestbookModal.tsx rename to src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/DownloadWithTermsAndGuestbookModal.tsx index f1f0f93dd..496eddc52 100644 --- a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/DownloadWithGuestbookModal.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/DownloadWithTermsAndGuestbookModal.tsx @@ -20,7 +20,7 @@ import { import { downloadFromSignedUrl } from '@/shared/helpers/DownloadHelper' import { FileDownloadMode } from '@/files/domain/models/FileMetadata' -interface DownloadWithGuestbookModalProps { +interface DownloadWithTermsAndGuestbookModalProps { fileId?: number | string fileIds?: Array format?: string | FileDownloadMode @@ -34,7 +34,7 @@ interface DownloadWithGuestbookModalProps { } type GuestbookFormValues = Record -export function DownloadWithGuestbookModal({ +export function DownloadWithTermsAndGuestbookModal({ fileId, fileIds, format, @@ -45,18 +45,19 @@ export function DownloadWithGuestbookModal({ datasetCustomTerms, show, handleClose -}: DownloadWithGuestbookModalProps) { +}: DownloadWithTermsAndGuestbookModalProps) { const { t: tFiles } = useTranslation('files') const { t: tDataset } = useTranslation('dataset') const { user } = useSession() const accessRepository = useAccessRepository() const guestbookRepository = useGuestbookRepository() + const hasGuestbook = guestbookId !== undefined const [formValues, setFormValues] = useState({}) const { guestbook, isLoadingGuestbook, errorGetGuestbook } = useGetGuestbookById({ guestbookRepository, guestbookId, - enabled: show + enabled: show && hasGuestbook }) const accountFieldKeys = useMemo(() => ['name', 'email', 'institution', 'position'], []) @@ -275,12 +276,14 @@ export function DownloadWithGuestbookModal({ onClick={() => void handleSubmit({ hasFormErrors: hasAccountFieldErrors || hasCustomQuestionErrors, - guestbook, guestbookResponse: buildGuestbookResponse() }) } disabled={ - isLoadingGuestbook || isSubmittingGuestbook || !!errorGetGuestbook || !guestbook + isLoadingGuestbook || + isSubmittingGuestbook || + !!errorGetGuestbook || + (hasGuestbook && !guestbook) } aria-label={tFiles('requestAccess.confirmation')}> {isSubmittingGuestbook ? : null}{' '} diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/GuestbookCollectForm.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/GuestbookCollectForm.tsx index 4150a5efa..0acfbff3c 100644 --- a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/GuestbookCollectForm.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/GuestbookCollectForm.tsx @@ -6,7 +6,7 @@ import { CustomTerms as CustomTermsModel, DatasetLicense } from '@/dataset/domai import { Validator as validator } from '@/shared/helpers/Validator' import { CustomTerms } from '@/sections/dataset/dataset-terms/CustomTerms' import { QueryParamKey, Route } from '@/sections/Route.enum' -import styles from './DownloadWithGuestbookModal.module.scss' +import styles from './DownloadWithTermsAndGuestbookModal.module.scss' interface GuestbookCollectFormProps { license?: DatasetLicense @@ -176,45 +176,46 @@ export function GuestbookCollectForm({ - {accountFieldKeys.map((accountFieldKey) => { - const fieldLabel = tGuestbooks(`create.fields.dataCollected.options.${accountFieldKey}`) - const isRequired = isAccountFieldRequired(accountFieldKey) - const isIdentityField = accountFieldKey === 'name' || accountFieldKey === 'email' - const shouldDisableInput = shouldLockIdentityFields && isIdentityField - const invalid = hasAttemptedAccept && accountFieldErrors[accountFieldKey] !== null + {guestbook && + accountFieldKeys.map((accountFieldKey) => { + const fieldLabel = tGuestbooks(`create.fields.dataCollected.options.${accountFieldKey}`) + const isRequired = isAccountFieldRequired(accountFieldKey) + const isIdentityField = accountFieldKey === 'name' || accountFieldKey === 'email' + const shouldDisableInput = shouldLockIdentityFields && isIdentityField + const invalid = hasAttemptedAccept && accountFieldErrors[accountFieldKey] !== null - return ( - - - - {fieldLabel} - {isRequired && *} - - - - - onFieldChange(accountFieldKey, (event.target as HTMLInputElement).value) - } - isInvalid={invalid} - disabled={shouldDisableInput} - aria-required={isRequired} - /> - - {invalid ? accountFieldErrors[accountFieldKey] : undefined} - - - - ) - })} + return ( + + + + {fieldLabel} + {isRequired && *} + + + + + onFieldChange(accountFieldKey, (event.target as HTMLInputElement).value) + } + isInvalid={invalid} + disabled={shouldDisableInput} + aria-required={isRequired} + /> + + {invalid ? accountFieldErrors[accountFieldKey] : undefined} + + + + ) + })} - {customQuestions.length > 0 && ( + {guestbook && customQuestions.length > 0 && ( diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/useGuestbookCollectSubmission.ts b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/useGuestbookCollectSubmission.ts index 52d829a1a..0dcecb195 100644 --- a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/useGuestbookCollectSubmission.ts +++ b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/useGuestbookCollectSubmission.ts @@ -10,7 +10,6 @@ import { AccessRepository, GuestbookResponseDTO } from '@/access/domain/repositories/AccessRepository' -import { Guestbook } from '@/guestbooks/domain/models/Guestbook' import { JSDataverseWriteErrorHandler } from '@/shared/helpers/JSDataverseWriteErrorHandler' interface UseGuestbookCollectSubmissionProps { @@ -25,7 +24,6 @@ interface UseGuestbookCollectSubmissionProps { interface HandleSubmitProps { hasFormErrors: boolean - guestbook?: Guestbook guestbookResponse: GuestbookResponseDTO } @@ -62,11 +60,11 @@ export const useGuestbookCollectSubmission = ({ : undefined const handleSubmit = useCallback( - async ({ hasFormErrors, guestbook, guestbookResponse }: HandleSubmitProps) => { + async ({ hasFormErrors, guestbookResponse }: HandleSubmitProps) => { setHasAttemptedAccept(true) setErrorDownloadSignedUrlFile(null) - if (hasFormErrors || !guestbook) { + if (hasFormErrors) { return } diff --git a/src/sections/file/file-action-buttons/access-file-menu/FileDownloadOptions.tsx b/src/sections/file/file-action-buttons/access-file-menu/FileDownloadOptions.tsx index 888c48e6e..258fbaaac 100644 --- a/src/sections/file/file-action-buttons/access-file-menu/FileDownloadOptions.tsx +++ b/src/sections/file/file-action-buttons/access-file-menu/FileDownloadOptions.tsx @@ -13,9 +13,10 @@ import { useDataset } from '@/sections/dataset/DatasetContext' import { CustomTerms, DatasetLicense, - DatasetPublishingStatus + DatasetPublishingStatus, + defaultLicense } from '@/dataset/domain/models/Dataset' -import { DownloadWithGuestbookModal } from '@/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/DownloadWithGuestbookModal' +import { DownloadWithTermsAndGuestbookModal } from '@/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/DownloadWithTermsAndGuestbookModal' interface FileDownloadOptionsProps { fileId: number @@ -48,7 +49,8 @@ export function FileDownloadOptions({ }: FileDownloadOptionsProps) { const { t } = useTranslation('files') const { dataset } = useDataset() - const [showDownloadWithGuestbookModal, setShowDownloadWithGuestbookModal] = useState(false) + const [showDownloadWithTermsAndGuestbookModal, setShowDownloadWithTermsAndGuestbookModal] = + useState(false) const [selectedDownloadFormat, setSelectedDownloadFormat] = useState( FileDownloadMode.ORIGINAL ) @@ -57,7 +59,6 @@ export function FileDownloadOptions({ return <> } - const resolvedGuestbookId = guestbookId ?? dataset?.guestbookId const resolvedDatasetPersistentId = datasetPersistentId ?? dataset?.persistentId const resolvedDatasetLicense = datasetLicense ?? dataset?.license const resolvedDatasetCustomTerms = datasetCustomTerms ?? dataset?.termsOfUse?.customTerms @@ -65,12 +66,17 @@ export function FileDownloadOptions({ const resolvedIsDraftDataset = isDraft ?? dataset?.version.publishingStatus === DatasetPublishingStatus.DRAFT const resolvedCanEdit = canEdit ?? dataset?.permissions.canUpdateDataset ?? false - const hasGuestbook = - resolvedGuestbookId !== undefined && !resolvedIsDraftDataset && !resolvedCanEdit + const bypassTermsGuard = resolvedIsDraftDataset || resolvedCanEdit + const hasGuestbook = guestbookId !== undefined + const hasNonDefaultLicense = + resolvedDatasetLicense !== undefined && resolvedDatasetLicense.name !== defaultLicense.name + const hasCustomTerms = resolvedDatasetCustomTerms !== undefined + const shouldShowModal = + !bypassTermsGuard && (hasGuestbook || hasCustomTerms || hasNonDefaultLicense) const openGuestbookModal = (format: string | FileDownloadMode) => { setSelectedDownloadFormat(format) - setShowDownloadWithGuestbookModal(true) + setShowDownloadWithTermsAndGuestbookModal(true) } return ( @@ -84,14 +90,14 @@ export function FileDownloadOptions({ type={type} ingestInProgress={ingestInProgress} downloadUrls={downloadUrls} - hasGuestbook={hasGuestbook} + requiresTermsOrGuestbook={shouldShowModal} onOpenGuestbookModal={openGuestbookModal} isLockedFromFileDownload={isLockedFromFileDownload} /> ) : ( openGuestbookModal(FileDownloadMode.ORIGINAL)} type={type} ingestIsInProgress={ingestInProgress} @@ -99,16 +105,16 @@ export function FileDownloadOptions({ isLockedFromFileDownload={isLockedFromFileDownload} /> )} - {hasGuestbook && showDownloadWithGuestbookModal && ( - setShowDownloadWithGuestbookModal(false)} + show={showDownloadWithTermsAndGuestbookModal} + handleClose={() => setShowDownloadWithTermsAndGuestbookModal(false)} /> )} diff --git a/src/sections/file/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.tsx b/src/sections/file/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.tsx index 27369dbe0..01992f9a2 100644 --- a/src/sections/file/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.tsx +++ b/src/sections/file/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.tsx @@ -12,7 +12,7 @@ import { interface FileNonTabularDownloadOptionsProps { fileId: number - hasGuestbook: boolean + requiresTermsOrGuestbook: boolean onOpenGuestbookModal: () => void type: FileType downloadUrlOriginal: string @@ -22,7 +22,7 @@ interface FileNonTabularDownloadOptionsProps { export function FileNonTabularDownloadOptions({ fileId, - hasGuestbook, + requiresTermsOrGuestbook, onOpenGuestbookModal, type, ingestIsInProgress, @@ -37,7 +37,7 @@ export function FileNonTabularDownloadOptions({ return } - if (hasGuestbook) { + if (requiresTermsOrGuestbook) { event.preventDefault() onOpenGuestbookModal() return diff --git a/src/sections/file/file-action-buttons/access-file-menu/FileTabularDownloadOptions.tsx b/src/sections/file/file-action-buttons/access-file-menu/FileTabularDownloadOptions.tsx index 63ab95a88..86b7787fe 100644 --- a/src/sections/file/file-action-buttons/access-file-menu/FileTabularDownloadOptions.tsx +++ b/src/sections/file/file-action-buttons/access-file-menu/FileTabularDownloadOptions.tsx @@ -17,7 +17,7 @@ interface FileTabularDownloadOptionsProps { type: FileType ingestInProgress: boolean downloadUrls: FileDownloadUrls - hasGuestbook: boolean + requiresTermsOrGuestbook: boolean onOpenGuestbookModal: (format: string) => void isLockedFromFileDownload: boolean } @@ -27,7 +27,7 @@ export function FileTabularDownloadOptions({ type, ingestInProgress, downloadUrls, - hasGuestbook, + requiresTermsOrGuestbook, onOpenGuestbookModal, isLockedFromFileDownload }: FileTabularDownloadOptionsProps) { @@ -42,7 +42,7 @@ export function FileTabularDownloadOptions({ event.preventDefault() - if (hasGuestbook) { + if (requiresTermsOrGuestbook) { onOpenGuestbookModal(format) return } diff --git a/src/stories/guestbooks/guestbook-applied-modal/DownloadWithGuestbookModal.stories.tsx b/src/stories/guestbooks/guestbook-applied-modal/DownloadWithTermsAndGuestbookModal.stories.tsx similarity index 83% rename from src/stories/guestbooks/guestbook-applied-modal/DownloadWithGuestbookModal.stories.tsx rename to src/stories/guestbooks/guestbook-applied-modal/DownloadWithTermsAndGuestbookModal.stories.tsx index e99810440..8cea38094 100644 --- a/src/stories/guestbooks/guestbook-applied-modal/DownloadWithGuestbookModal.stories.tsx +++ b/src/stories/guestbooks/guestbook-applied-modal/DownloadWithTermsAndGuestbookModal.stories.tsx @@ -1,5 +1,5 @@ import { Meta, StoryObj } from '@storybook/react' -import { DownloadWithGuestbookModal } from '@/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/DownloadWithGuestbookModal' +import { DownloadWithTermsAndGuestbookModal } from '@/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/DownloadWithTermsAndGuestbookModal' import { WithI18next } from '@/stories/WithI18next' import { WithLoggedInUser } from '@/stories/WithLoggedInUser' import { GuestbookMockRepository } from '@/stories/shared-mock-repositories/guestbook/GuestbookMockRepository' @@ -25,9 +25,9 @@ const accessRepository: AccessRepository = { const guestbookRepository = new GuestbookMockRepository() -const meta: Meta = { - title: 'Sections/Guestbooks/DownloadWithGuestbookModal', - component: DownloadWithGuestbookModal, +const meta: Meta = { + title: 'Sections/Guestbooks/DownloadWithTermsAndGuestbookModal', + component: DownloadWithTermsAndGuestbookModal, decorators: [ WithI18next, WithLoggedInUser, @@ -49,7 +49,7 @@ const meta: Meta = { } export default meta -type Story = StoryObj +type Story = StoryObj export const SingleFile: Story = { args: { diff --git a/tests/component/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.spec.tsx b/tests/component/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.spec.tsx index 63b2afae5..639811886 100644 --- a/tests/component/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.spec.tsx +++ b/tests/component/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.spec.tsx @@ -9,6 +9,7 @@ import { getGuestbook, submitGuestbookForDatasetDownload } from '@iqss/dataverse import { ReactNode, Suspense } from 'react' import { useTranslation } from 'react-i18next' import { AccessRepository } from '@/access/domain/repositories/AccessRepository' +import { DatasetPermissions } from '@/dataset/domain/models/Dataset' import { AccessRepositoryProvider } from '@/sections/access/AccessRepositoryProvider' function TranslationPreloader({ children }: { children: ReactNode }) { @@ -287,7 +288,7 @@ describe('AccessDatasetMenu', () => { cy.findByRole('button', { name: 'Access Dataset' }).should('not.exist') }) - it('opens DownloadWithGuestbookModal when guestbook exists and download option is clicked', () => { + it('opens DownloadWithTermsAndGuestbookModal when guestbook exists and download option is clicked', () => { const version = DatasetVersionMother.createReleased() const permissions = DatasetPermissionsMother.createWithFilesDownloadAllowedButNotUpdatePermissions() @@ -433,7 +434,7 @@ describe('AccessDatasetMenu', () => { cy.findByRole('dialog').should('exist') }) - it('closes DownloadWithGuestbookModal when cancel is clicked', () => { + it('closes DownloadWithTermsAndGuestbookModal when cancel is clicked', () => { const version = DatasetVersionMother.createReleased() const permissions = DatasetPermissionsMother.createWithFilesDownloadAllowedButNotUpdatePermissions() @@ -680,4 +681,171 @@ describe('AccessDatasetMenu', () => { cy.findByRole('dialog').should('not.exist') cy.findByText('Your download has started.').should('exist') }) + + it('opens the terms modal when custom terms exist without a guestbook', () => { + const version = DatasetVersionMother.createReleased() + const permissions: DatasetPermissions = + DatasetPermissionsMother.createWithFilesDownloadAllowedButNotUpdatePermissions() + const fileDownloadSizes = [ + DatasetFileDownloadSizeMother.createOriginal({ value: 2000, unit: FileSizeUnit.BYTES }) + ] + + cy.customMount( + withAccessRepository( + + + + + + ) + ) + + cy.findByRole('button', { name: 'Access Dataset' }).click() + cy.findByRole('button', { name: /Download ZIP/ }) + .should('exist') + .click() + + cy.findByRole('dialog').should('exist') + cy.findByText('Custom Dataset Terms').should('exist') + // Guestbook fields should not be visible since there is no guestbook + cy.findByLabelText(/name/i).should('not.exist') + cy.findByLabelText(/email/i).should('not.exist') + }) + + it('opens the terms modal when CC BY 4.0 license exists without a guestbook', () => { + const version = DatasetVersionMother.createReleased() + const permissions: DatasetPermissions = + DatasetPermissionsMother.createWithFilesDownloadAllowedButNotUpdatePermissions() + const fileDownloadSizes = [ + DatasetFileDownloadSizeMother.createOriginal({ value: 2000, unit: FileSizeUnit.BYTES }) + ] + + cy.customMount( + withAccessRepository( + + + + + + ) + ) + + cy.findByRole('button', { name: 'Access Dataset' }).click() + cy.findByRole('button', { name: /Download ZIP/ }) + .should('exist') + .click() + + cy.findByRole('dialog').should('exist') + // Guestbook fields should not be visible since there is no guestbook + cy.findByLabelText(/name/i).should('not.exist') + cy.findByLabelText(/email/i).should('not.exist') + }) + + it('downloads directly when custom terms exist but user can edit the dataset', () => { + const version = DatasetVersionMother.createReleased() + const permissions = DatasetPermissionsMother.create({ + canDownloadFiles: true, + canUpdateDataset: true + }) + const fileDownloadSizes = [ + DatasetFileDownloadSizeMother.createOriginal({ value: 2000, unit: FileSizeUnit.BYTES }) + ] + + cy.window().then((window) => { + cy.stub(window.HTMLAnchorElement.prototype, 'click').as('anchorClick') + }) + + cy.customMount( + withAccessRepository( + + + + + + ) + ) + + cy.findByRole('button', { name: 'Access Dataset' }).click() + cy.findByRole('button', { name: /Download ZIP/ }).click() + + cy.get('@anchorClick').should('have.been.calledOnce') + cy.findByRole('dialog').should('not.exist') + cy.findByText('Your download has started.').should('exist') + }) + + it('bypasses the custom terms modal for draft datasets', () => { + const version = DatasetVersionMother.createDraft() + const permissions = + DatasetPermissionsMother.createWithFilesDownloadAllowedButNotUpdatePermissions() + const fileDownloadSizes = [ + DatasetFileDownloadSizeMother.createOriginal({ value: 2000, unit: FileSizeUnit.BYTES }) + ] + + cy.window().then((window) => { + cy.stub(window.HTMLAnchorElement.prototype, 'click').as('anchorClick') + }) + + cy.customMount( + withAccessRepository( + + + + + + ) + ) + + cy.findByRole('button', { name: 'Access Dataset' }).click() + cy.findByRole('button', { name: /Download ZIP/ }).click() + + cy.get('@anchorClick').should('have.been.calledOnce') + cy.findByRole('dialog').should('not.exist') + cy.findByText('Your download has started.').should('exist') + }) }) diff --git a/tests/component/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.spec.tsx index 0845f76a8..077c3ae3c 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.spec.tsx @@ -20,6 +20,10 @@ import { FileMetadataMother } from '../../../../../../files/domain/models/FileMe import { MultipleFileDownloadProvider } from '../../../../../../../../src/sections/file/multiple-file-download/MultipleFileDownloadProvider' import { FileRepository } from '../../../../../../../../src/files/domain/repositories/FileRepository' import { FilePreviewMother } from '../../../../../../files/domain/models/FilePreviewMother' +import { + CustomTermsMother, + TermsOfUseMother +} from '../../../../../../dataset/domain/models/TermsOfUseMother' const datasetRepository: DatasetRepository = {} as DatasetRepository const fileRepository = {} as FileRepository @@ -642,6 +646,104 @@ describe('DownloadFilesButton', () => { cy.findByText('Your download has started.').should('exist') }) + it('opens the terms modal when custom terms exist without a guestbook for non-tabular files', () => { + const datasetWithCustomTerms = DatasetMother.create({ + permissions: DatasetPermissionsMother.createWithFilesDownloadAllowedButNotUpdatePermissions(), + hasOneTabularFileAtLeast: false, + termsOfUse: TermsOfUseMother.create({ + customTerms: CustomTermsMother.create({ termsOfUse: 'Custom terms for bulk download' }) + }) + }) + const files = FilePreviewMother.createMany(2, { + metadata: FileMetadataMother.createNonTabular() + }) + const fileSelection = { + 'some-file-id': files[0] + } + + cy.mountAuthenticated( + withDataset( + , + datasetWithCustomTerms + ) + ) + + cy.get('#download-files').click() + cy.findByRole('dialog').should('exist') + cy.findByText('Custom terms for bulk download').should('exist') + // Guestbook fields should not be visible since there is no guestbook + cy.findByLabelText(/^Name/).should('not.exist') + cy.findByLabelText(/^Email/).should('not.exist') + cy.findByRole('button', { name: 'Accept' }).should('exist').and('not.be.disabled') + }) + + it('opens the terms modal when custom terms exist without a guestbook for tabular files', () => { + const datasetWithCustomTerms = DatasetMother.create({ + permissions: DatasetPermissionsMother.createWithFilesDownloadAllowedButNotUpdatePermissions(), + hasOneTabularFileAtLeast: true, + termsOfUse: TermsOfUseMother.create({ + customTerms: CustomTermsMother.create({ termsOfUse: 'Custom terms for bulk download' }) + }) + }) + const files = FilePreviewMother.createMany(2, { + metadata: FileMetadataMother.createTabular() + }) + const fileSelection = { + 'some-file-id': files[0] + } + + cy.mountAuthenticated( + withDataset( + , + datasetWithCustomTerms + ) + ) + + cy.get('#download-files').click() + cy.findByRole('button', { name: 'Original Format' }).click() + cy.findByRole('dialog').should('exist') + cy.findByText('Custom terms for bulk download').should('exist') + cy.findByRole('button', { name: 'Accept' }).should('exist').and('not.be.disabled') + }) + + it('bypasses the terms modal for editors even when custom terms exist', () => { + const datasetWithCustomTerms = DatasetMother.create({ + permissions: DatasetPermissionsMother.create({ + canDownloadFiles: true, + canUpdateDataset: true + }), + hasOneTabularFileAtLeast: false, + termsOfUse: TermsOfUseMother.create({ + customTerms: CustomTermsMother.create({ termsOfUse: 'Custom terms for bulk download' }) + }) + }) + const files = FilePreviewMother.createMany(2, { + metadata: FileMetadataMother.createNonTabular() + }) + const fileSelection = { + 'some-file-id': files[0] + } + + cy.window().then((window) => { + cy.stub(window.HTMLAnchorElement.prototype, 'click').as('anchorClick') + }) + + cy.mountAuthenticated( + withAccessRepository( + withDataset( + , + datasetWithCustomTerms + ) + ) + ) + + cy.get('#download-files').click() + + cy.get('@anchorClick').should('have.been.calledOnce') + cy.findByRole('dialog').should('not.exist') + cy.findByText('Your download has started.').should('exist') + }) + it('does not render the AccessDatasetMenu if the file store does not start with "s3"', () => { const datasetWithDownloadFilesPermission = DatasetMother.create({ permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed(), diff --git a/tests/component/sections/dataset/dataset-files/guestbook/DownloadWithGuestbookModal.spec.tsx b/tests/component/sections/dataset/dataset-files/guestbook/DownloadWithTermsAndGuestbookModal.spec.tsx similarity index 87% rename from tests/component/sections/dataset/dataset-files/guestbook/DownloadWithGuestbookModal.spec.tsx rename to tests/component/sections/dataset/dataset-files/guestbook/DownloadWithTermsAndGuestbookModal.spec.tsx index 8dee82cae..121d918d7 100644 --- a/tests/component/sections/dataset/dataset-files/guestbook/DownloadWithGuestbookModal.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/guestbook/DownloadWithTermsAndGuestbookModal.spec.tsx @@ -1,4 +1,4 @@ -import { DownloadWithGuestbookModal } from '@/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/DownloadWithGuestbookModal' +import { DownloadWithTermsAndGuestbookModal } from '@/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/DownloadWithTermsAndGuestbookModal' import { DatasetContext } from '@/sections/dataset/DatasetContext' import { Guestbook } from '@/guestbooks/domain/models/Guestbook' import { DatasetLicense } from '@/dataset/domain/models/Dataset' @@ -90,7 +90,7 @@ const datasetLicense: DatasetLicense = { uri: 'https://creativecommons.org/publicdomain/zero/1.0/' } -describe('DownloadWithGuestbookModal', () => { +describe('DownloadWithTermsAndGuestbookModal', () => { let getGuestbookImpl: (guestbookId: number) => Promise let submitGuestbookForDatafileDownloadImpl: ( fileId: number | string, @@ -176,7 +176,7 @@ describe('DownloadWithGuestbookModal', () => { it('renders modal title and actions', () => { cy.customMount( withAnonymousSession( - { it('renders dataset terms and license when they are provided', () => { cy.customMount( withAnonymousSession( - { it('renders custom dataset terms when custom terms are available', () => { cy.customMount( withAnonymousSession( - { ) }) - it('keeps accept disabled when no guestbook is loaded', () => { + it('enables accept when no guestbook but custom terms exist (custom terms only)', () => { cy.customMount( withAnonymousSession( - ) ) - cy.findByRole('button', { name: 'Accept' }).should('be.disabled') + cy.findByRole('button', { name: 'Accept' }).should('not.be.disabled') + cy.findByText('Custom terms for this dataset').should('exist') + // Guestbook fields should not be shown + cy.findByLabelText(/^Name/).should('not.exist') + cy.findByLabelText(/^Email/).should('not.exist') + }) + + it('downloads file when accepting custom terms only (no guestbook)', () => { + const handleClose = cy.stub().as('handleClose') + + cy.window().then((window) => { + cy.stub(window.HTMLAnchorElement.prototype, 'click').as('anchorClick') + }) + + cy.customMount( + withAnonymousSession( + + ) + ) + + cy.findByRole('button', { name: 'Accept' }).click() + + cy.get('@submitGuestbookForDatafileDownload').should('have.been.calledOnce') + cy.get('@anchorClick').should('have.been.calledOnce') + cy.get('@handleClose').should('have.been.calledOnce') + cy.findByText('Your download has started.').should('exist') }) it('calls handleClose when clicking cancel', () => { @@ -257,7 +293,7 @@ describe('DownloadWithGuestbookModal', () => { cy.customMount( withAnonymousSession( - { cy.customMount( withAnonymousSession( - + ) ) @@ -306,7 +347,12 @@ describe('DownloadWithGuestbookModal', () => { cy.customMount( withAnonymousSession( - + ) ) @@ -334,7 +380,12 @@ describe('DownloadWithGuestbookModal', () => { cy.customMount( withAnonymousSession( - + ) ) @@ -359,7 +410,7 @@ describe('DownloadWithGuestbookModal', () => { cy.customMount( withAnonymousSession( - { cy.customMount( withAnonymousSession( - { it('disables name and email fields for authenticated users', () => { cy.mountAuthenticated( withRepositories( - { cy.customMount( withAnonymousSession( - { cy.customMount( withAnonymousSession( - { it('shows required field validation after clicking accept', () => { cy.customMount( withAnonymousSession( - { it('does not show required field validation before clicking accept', () => { cy.customMount( withAnonymousSession( - { cy.customMount( withAnonymousSession( - { cy.customMount( withAnonymousSession( - + ) ) @@ -618,7 +674,12 @@ describe('DownloadWithGuestbookModal', () => { cy.customMount( withAnonymousSession( - + ) ) @@ -666,7 +727,12 @@ describe('DownloadWithGuestbookModal', () => { cy.customMount( withAnonymousSession( - + ) ) @@ -686,7 +752,7 @@ describe('DownloadWithGuestbookModal', () => { cy.customMount( withAnonymousSession( - { cy.customMount( withAnonymousSession( - { cy.customMount( withAnonymousSession( - { cy.customMount( withAnonymousSession( - { cy.customMount( withAnonymousSession( - { cy.findByRole('dialog').should('not.exist') cy.findByText('Your download has started.').should('exist') }) + + it('opens the terms modal when custom terms exist without a guestbook', () => { + const guestbookRepository: GuestbookRepository = { + getGuestbook: cy.stub().as('getGuestbook').resolves(guestbook), + getGuestbooksByCollectionId: cy.stub().resolves([]), + assignDatasetGuestbook: cy.stub().resolves(), + removeDatasetGuestbook: cy.stub().resolves() + } + const accessRepository: AccessRepository = { + submitGuestbookForDatasetDownload: cy.stub().resolves('signed-url-dataset'), + submitGuestbookForDatafileDownload: cy + .stub() + .as('submitGuestbookForDatafileDownload') + .resolves('signed-url-file'), + submitGuestbookForDatafilesDownload: cy.stub().resolves('signed-url-datafiles') + } + + cy.customMount( + + + + + + + + + ) + + cy.findByRole('button', { name: 'Access File' }).click() + cy.findByText('Download Options').should('exist') + cy.findByRole('button', { name: 'Plain Text' }).click() + + cy.get('@getGuestbook').should('not.have.been.called') + cy.findByRole('dialog').should('exist') + cy.findByText('Custom Dataset Terms').should('exist') + // Guestbook fields should not be visible since there is no guestbook + cy.findByLabelText(/^Name/).should('not.exist') + cy.findByLabelText(/^Email/).should('not.exist') + cy.findByRole('button', { name: 'Accept' }).should('exist').and('not.be.disabled') + }) + + it('bypasses the custom terms modal for files in draft datasets', () => { + const accessRepository: AccessRepository = { + submitGuestbookForDatasetDownload: cy.stub().resolves('signed-url-dataset'), + submitGuestbookForDatafileDownload: cy + .stub() + .as('submitGuestbookForDatafileDownload') + .resolves('signed-url-file'), + submitGuestbookForDatafilesDownload: cy.stub().resolves('signed-url-datafiles') + } + + cy.window().then((window) => { + cy.stub(window.HTMLAnchorElement.prototype, 'click').as('anchorClick') + }) + + cy.customMount( + + + + + + + ) + + cy.findByRole('button', { name: 'Access File' }).click() + cy.findByRole('button', { name: 'Plain Text' }).click() + + cy.get('@submitGuestbookForDatafileDownload').should('have.been.calledOnce') + cy.get('@anchorClick').should('have.been.calledOnce') + cy.findByRole('dialog').should('not.exist') + cy.findByText('Your download has started.').should('exist') + }) + + it('bypasses the custom terms modal for users who can edit the dataset', () => { + const accessRepository: AccessRepository = { + submitGuestbookForDatasetDownload: cy.stub().resolves('signed-url-dataset'), + submitGuestbookForDatafileDownload: cy + .stub() + .as('submitGuestbookForDatafileDownload') + .resolves('signed-url-file'), + submitGuestbookForDatafilesDownload: cy.stub().resolves('signed-url-datafiles') + } + + cy.window().then((window) => { + cy.stub(window.HTMLAnchorElement.prototype, 'click').as('anchorClick') + }) + + cy.customMount( + + + + + + + ) + + cy.findByRole('button', { name: 'Access File' }).click() + cy.findByRole('button', { name: 'Plain Text' }).click() + + cy.get('@submitGuestbookForDatafileDownload').should('have.been.calledOnce') + cy.get('@anchorClick').should('have.been.calledOnce') + cy.findByRole('dialog').should('not.exist') + cy.findByText('Your download has started.').should('exist') + }) }) diff --git a/tests/component/sections/file/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.spec.tsx b/tests/component/sections/file/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.spec.tsx index 9c10b9200..f9a19cf46 100644 --- a/tests/component/sections/file/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.spec.tsx +++ b/tests/component/sections/file/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.spec.tsx @@ -35,7 +35,7 @@ describe('FileNonTabularDownloadOptions', () => { cy.customMount( { cy.customMount( { cy.customMount( { cy.customMount( { cy.customMount( { withAccessRepository( { fileId={defaultProps.fileId} type={tabularType} downloadUrls={downloadUrls} - hasGuestbook={false} + requiresTermsOrGuestbook={false} onOpenGuestbookModal={cy.stub()} ingestInProgress={false} isLockedFromFileDownload={defaultProps.isLockedFromFileDownload} @@ -40,7 +40,7 @@ describe('FileTabularDownloadOptions', () => { fileId={defaultProps.fileId} type={unknownType} downloadUrls={downloadUrls} - hasGuestbook={false} + requiresTermsOrGuestbook={false} onOpenGuestbookModal={cy.stub()} ingestInProgress={false} isLockedFromFileDownload={defaultProps.isLockedFromFileDownload} @@ -60,7 +60,7 @@ describe('FileTabularDownloadOptions', () => { fileId={defaultProps.fileId} type={tabularType} downloadUrls={downloadUrls} - hasGuestbook={false} + requiresTermsOrGuestbook={false} onOpenGuestbookModal={cy.stub()} ingestInProgress isLockedFromFileDownload={defaultProps.isLockedFromFileDownload} @@ -82,7 +82,7 @@ describe('FileTabularDownloadOptions', () => { fileId={defaultProps.fileId} type={tabularType} downloadUrls={downloadUrls} - hasGuestbook={false} + requiresTermsOrGuestbook={false} onOpenGuestbookModal={cy.stub()} ingestInProgress={false} isLockedFromFileDownload @@ -105,7 +105,7 @@ describe('FileTabularDownloadOptions', () => { fileId={defaultProps.fileId} type={rDataType} downloadUrls={downloadUrls} - hasGuestbook={false} + requiresTermsOrGuestbook={false} onOpenGuestbookModal={cy.stub()} ingestInProgress={false} isLockedFromFileDownload={defaultProps.isLockedFromFileDownload} @@ -129,7 +129,7 @@ describe('FileTabularDownloadOptions', () => { fileId={defaultProps.fileId} type={tabularType} downloadUrls={downloadUrls} - hasGuestbook + requiresTermsOrGuestbook onOpenGuestbookModal={onOpenGuestbookModal} ingestInProgress={false} isLockedFromFileDownload={defaultProps.isLockedFromFileDownload} @@ -149,7 +149,7 @@ describe('FileTabularDownloadOptions', () => { fileId={defaultProps.fileId} type={tabularType} downloadUrls={downloadUrls} - hasGuestbook + requiresTermsOrGuestbook onOpenGuestbookModal={onOpenGuestbookModal} ingestInProgress={false} isLockedFromFileDownload={defaultProps.isLockedFromFileDownload} diff --git a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx index f6d4608d9..bc65d204e 100644 --- a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx +++ b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx @@ -15,6 +15,7 @@ type Dataset = { datasetVersion: { metadataBlocks: { citation: { fields: { value: string }[] } } } } const DRAFT_PARAM = DatasetNonNumericVersionSearchParam.DRAFT +const FRONTEND_BASE_PATH = '/spa' describe('Dataset', () => { beforeEach(() => { @@ -28,7 +29,9 @@ describe('Dataset', () => { cy.wrap(DatasetHelper.create()) .its('persistentId') .then((persistentId: string) => { - cy.visit(`/spa/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}`) + cy.visit( + `${FRONTEND_BASE_PATH}/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}` + ) cy.fixture('dataset-finch1.json').then((dataset: Dataset) => { cy.findByRole('heading', { name: dataset.datasetVersion.metadataBlocks.citation.fields[0].value @@ -54,7 +57,9 @@ describe('Dataset', () => { cy.wrap(DatasetHelper.create(collectionId)) .its('persistentId') .then((persistentId: string) => { - cy.visit(`/spa/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}`) + cy.visit( + `${FRONTEND_BASE_PATH}/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}` + ) cy.findByText('Draft').should('exist') cy.findByRole('button', { name: 'Publish Dataset' }).should('exist').click() cy.findByRole('button', { name: 'Publish' }).should('exist') @@ -71,7 +76,9 @@ describe('Dataset', () => { cy.wrap(DatasetHelper.create()) .its('persistentId') .then((persistentId: string) => { - cy.visit(`/spa/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}`) + cy.visit( + `${FRONTEND_BASE_PATH}/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}` + ) cy.findByText('Draft').should('exist') cy.findByRole('button', { name: 'Publish Dataset' }).should('exist').click() cy.findByRole('button', { name: 'Publish' }).should('exist') @@ -87,7 +94,7 @@ describe('Dataset', () => { cy.wrap(DatasetHelper.create().then((dataset) => DatasetHelper.publish(dataset.persistentId))) .its('persistentId') .then((persistentId: string) => { - cy.visit(`/spa/datasets?persistentId=${persistentId}`) + cy.visit(`${FRONTEND_BASE_PATH}/datasets?persistentId=${persistentId}`) cy.findByText('Version 1.0').should('exist') cy.findByRole('button', { name: 'Edit Dataset' }).should('exist').click() cy.findByRole('button', { name: 'Metadata' }).should('exist').click() @@ -110,7 +117,7 @@ describe('Dataset', () => { cy.wrap(DatasetHelper.create().then((dataset) => DatasetHelper.publish(dataset.persistentId))) .its('persistentId') .then((persistentId: string) => { - cy.visit(`/spa/datasets?persistentId=${persistentId}`) + cy.visit(`${FRONTEND_BASE_PATH}/datasets?persistentId=${persistentId}`) cy.findByText('Version 1.0').should('exist') cy.findByRole('button', { name: 'Edit Dataset' }).should('exist').click() cy.findByRole('button', { name: 'Metadata' }).should('exist').click() @@ -131,7 +138,7 @@ describe('Dataset', () => { cy.wrap(DatasetHelper.create().then((dataset) => DatasetHelper.publish(dataset.persistentId))) .its('persistentId') .then((persistentId: string) => { - cy.visit(`/spa/datasets?persistentId=${persistentId}`) + cy.visit(`${FRONTEND_BASE_PATH}/datasets?persistentId=${persistentId}`) cy.findByText('Version 1.0').should('exist') cy.findByRole('button', { name: 'Edit Dataset' }).should('exist').click() cy.findByRole('button', { name: 'Metadata' }).should('exist').click() @@ -158,7 +165,7 @@ describe('Dataset', () => { .then((persistentId: string) => { TestsUtils.logout() cy.wait(1500) // Wait for the dataset to be published - cy.visit(`/spa/datasets?persistentId=${persistentId}`) + cy.visit(`${FRONTEND_BASE_PATH}/datasets?persistentId=${persistentId}`) cy.fixture('dataset-finch1.json').then((dataset: Dataset) => { cy.findByRole('heading', { @@ -179,7 +186,9 @@ describe('Dataset', () => { .its('persistentId') .then((persistentId: string) => { TestsUtils.logout() - cy.visit(`/spa/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}`) + cy.visit( + `${FRONTEND_BASE_PATH}/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}` + ) cy.findByTestId('not-found-page').should('exist') }) @@ -190,7 +199,7 @@ describe('Dataset', () => { .its('persistentId') .then((persistentId: string) => { cy.wait(1500) // Wait for the dataset to be published - cy.visit(`/spa/datasets?persistentId=${persistentId}&version=1.0`) + cy.visit(`${FRONTEND_BASE_PATH}/datasets?persistentId=${persistentId}&version=1.0`) cy.fixture('dataset-finch1.json').then((dataset: Dataset) => { cy.findByRole('heading', { @@ -208,7 +217,7 @@ describe('Dataset', () => { .its('persistentId') .then((persistentId: string) => { cy.wait(1500) // Wait for the dataset to be published - cy.visit(`/spa/datasets?persistentId=${persistentId}&version=2.0`) + cy.visit(`${FRONTEND_BASE_PATH}/datasets?persistentId=${persistentId}&version=2.0`) cy.fixture('dataset-finch1.json').then((dataset: Dataset) => { cy.findByRole('heading', { @@ -222,7 +231,7 @@ describe('Dataset', () => { }) it('loads page not found when passing a wrong persistentId', () => { - cy.visit('/spa/datasets?persistentId=doi:10.5072/FK2/WRONG') + cy.visit(`${FRONTEND_BASE_PATH}/datasets?persistentId=doi:10.5072/FK2/WRONG`) cy.findByTestId('not-found-page').should('exist') }) @@ -230,7 +239,7 @@ describe('Dataset', () => { cy.wrap(DatasetHelper.create().then((dataset) => DatasetHelper.createPrivateUrl(dataset.id))) .its('token') .then((token: string) => { - cy.visit(`/spa/datasets?privateUrlToken=${token}`) + cy.visit(`${FRONTEND_BASE_PATH}/datasets?privateUrlToken=${token}`) cy.fixture('dataset-finch1.json').then((dataset: Dataset) => { cy.findByRole('heading', { @@ -250,7 +259,7 @@ describe('Dataset', () => { ) .its('token') .then((token: string) => { - cy.visit(`/spa/datasets?privateUrlToken=${token}`) + cy.visit(`${FRONTEND_BASE_PATH}/datasets?privateUrlToken=${token}`) cy.fixture('dataset-finch1.json').then((dataset: Dataset) => { cy.findByRole('heading', { @@ -275,7 +284,7 @@ describe('Dataset', () => { .then(() => Promise.all([dataset, DatasetHelper.deaccession(dataset.id)])) }) .then(([dataset]: [DatasetResponse, { status: string }]) => { - cy.visit(`/spa/datasets?persistentId=${dataset.persistentId}`) + cy.visit(`${FRONTEND_BASE_PATH}/datasets?persistentId=${dataset.persistentId}`) cy.findByText(DatasetLabelValue.DEACCESSIONED).should('exist') }) @@ -289,7 +298,9 @@ describe('Dataset', () => { ) .its('persistentId') .then((persistentId: string) => { - cy.visit(`/spa/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}`) + cy.visit( + `${FRONTEND_BASE_PATH}/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}` + ) cy.findByText('Root').should('exist') cy.findByRole('link', { name: 'Scientific Research' }).should('exist').click() @@ -302,7 +313,9 @@ describe('Dataset', () => { cy.wrap(DatasetHelper.create()) .its('persistentId') .then((persistentId: string) => { - cy.visit(`/spa/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}`) + cy.visit( + `${FRONTEND_BASE_PATH}/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}` + ) cy.findByRole('button', { name: 'Edit Dataset' }).should('exist').click() cy.findByRole('button', { name: 'Delete Dataset' }).should('exist').click() @@ -322,7 +335,9 @@ describe('Dataset', () => { return { dataset, guestbook } }) ).then(({ dataset, guestbook }) => { - cy.visit(`/spa/datasets?persistentId=${dataset.persistentId}&version=${DRAFT_PARAM}`) + cy.visit( + `${FRONTEND_BASE_PATH}/datasets?persistentId=${dataset.persistentId}&version=${DRAFT_PARAM}` + ) cy.findByRole('tab', { name: /Terms and Guestbook/ }).click() cy.findByTestId('dataset-terms-guestbook-accordion-header') @@ -351,7 +366,9 @@ describe('Dataset', () => { cy.wrap(DatasetHelper.create()) .its('persistentId') .then((persistentId: string) => { - cy.visit(`/spa/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}`) + cy.visit( + `${FRONTEND_BASE_PATH}/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}` + ) cy.findByText('Files').should('exist') @@ -363,7 +380,9 @@ describe('Dataset', () => { cy.wrap(DatasetHelper.createWithFiles(FileHelper.createMany(3)), { timeout: 5000 }) .its('persistentId') .then((persistentId: string) => { - cy.visit(`/spa/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}`) + cy.visit( + `${FRONTEND_BASE_PATH}/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}` + ) cy.findByText('Files').should('exist') @@ -383,7 +402,9 @@ describe('Dataset', () => { cy.wrap(DatasetHelper.createWithFiles(FileHelper.createMany(30)), { timeout: 20000 }) .its('persistentId') .then((persistentId: string) => { - cy.visit(`/spa/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}`) + cy.visit( + `${FRONTEND_BASE_PATH}/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}` + ) cy.findByText('Files').should('exist') @@ -407,7 +428,9 @@ describe('Dataset', () => { cy.wrap(DatasetHelper.createWithFiles(FileHelper.createMany(30)), { timeout: 20000 }) .its('persistentId') .then((persistentId: string) => { - cy.visit(`/spa/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}`) + cy.visit( + `${FRONTEND_BASE_PATH}/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}` + ) cy.findByText('Files').should('exist') @@ -437,7 +460,9 @@ describe('Dataset', () => { cy.wrap(DatasetHelper.createWithFiles(FileHelper.createMany(3))) .its('persistentId') .then((persistentId: string) => { - cy.visit(`/spa/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}`) + cy.visit( + `${FRONTEND_BASE_PATH}/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}` + ) cy.findByText('Files').should('exist') @@ -459,7 +484,7 @@ describe('Dataset', () => { cy.wait(1500) // Wait for the dataset to be published TestsUtils.logout() - cy.visit(`/spa/datasets?persistentId=${persistentId}`) + cy.visit(`${FRONTEND_BASE_PATH}/datasets?persistentId=${persistentId}`) cy.findByText('Files').should('exist') @@ -474,7 +499,9 @@ describe('Dataset', () => { cy.wrap(DatasetHelper.createWithFiles(FileHelper.createManyRestricted(1))) .its('persistentId') .then((persistentId: string) => { - cy.visit(`/spa/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}`) + cy.visit( + `${FRONTEND_BASE_PATH}/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}` + ) cy.findByText('Files').should('exist') @@ -505,7 +532,7 @@ describe('Dataset', () => { TestsUtils.logout() - cy.visit(`/spa/datasets?persistentId=${persistentId}`) + cy.visit(`${FRONTEND_BASE_PATH}/datasets?persistentId=${persistentId}`) cy.wait(1500) // Wait for the files to be loaded @@ -540,7 +567,9 @@ describe('Dataset', () => { .then((persistentId: string) => { cy.wait(1500) // Wait for the files to be embargoed - cy.visit(`/spa/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}`) + cy.visit( + `${FRONTEND_BASE_PATH}/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}` + ) cy.wait(1500) // Wait for the files to be loaded @@ -598,7 +627,9 @@ describe('Dataset', () => { cy.wrap(DatasetHelper.createWithFiles(files)) .its('persistentId') .then((persistentId: string) => { - cy.visit(`/spa/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}`) + cy.visit( + `${FRONTEND_BASE_PATH}/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}` + ) cy.findByText('Files').should('exist') @@ -719,7 +750,9 @@ describe('Dataset', () => { .then((fileData) => cy.wrap(DatasetHelper.createWithFiles([fileData]))) .its('persistentId') .then((persistentId: string) => { - cy.visit(`/spa/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}`) + cy.visit( + `${FRONTEND_BASE_PATH}/datasets?persistentId=${persistentId}&version=${DRAFT_PARAM}` + ) cy.findByText('Files').should('exist') @@ -738,7 +771,7 @@ describe('Dataset', () => { .its('persistentId') .then((persistentId: string) => { cy.wait(1500) // Wait for the dataset to be published - cy.visit(`/spa/datasets?persistentId=${persistentId}`) + cy.visit(`${FRONTEND_BASE_PATH}/datasets?persistentId=${persistentId}`) cy.wait(1500) // Wait for the page to load cy.findByText('Files').should('exist') @@ -763,7 +796,7 @@ describe('Dataset', () => { .then((persistentId: string) => { TestsUtils.logout() cy.wait(1500) // Wait for the dataset to be published and the session to clear - cy.visit(`/spa/datasets?persistentId=${persistentId}`) + cy.visit(`${FRONTEND_BASE_PATH}/datasets?persistentId=${persistentId}`) cy.wait(1500) // Wait for the page to load cy.findByText('Files').should('exist') @@ -793,7 +826,7 @@ describe('Dataset', () => { return dataset }) ).then((publishedDataset) => { - cy.visit(`/spa/datasets?persistentId=${publishedDataset.persistentId}`) + cy.visit(`${FRONTEND_BASE_PATH}/datasets?persistentId=${publishedDataset.persistentId}`) cy.wait(1500) // Wait for the page to load cy.findByText('Files').should('exist') @@ -825,7 +858,7 @@ describe('Dataset', () => { }) ).then((publishedDataset) => { TestsUtils.logout() - cy.visit(`/spa/datasets?persistentId=${publishedDataset.persistentId}`) + cy.visit(`${FRONTEND_BASE_PATH}/datasets?persistentId=${publishedDataset.persistentId}`) cy.wait(1500) cy.findByText('Files').should('exist') @@ -841,6 +874,62 @@ describe('Dataset', () => { }) }) + it('opens the custom terms modal for guests when downloading a dataset with custom terms and no guestbook', () => { + cy.wrap( + DatasetHelper.createWithFiles(FileHelper.createMany(2)).then(async (dataset) => { + await DatasetHelper.setCustomTermsOfUse(dataset.id, { + termsOfUse: 'These are custom terms of use for testing' + }) + await DatasetHelper.publish(dataset.persistentId) + return dataset + }) + ).then((dataset) => { + TestsUtils.logout() + cy.visit(`${FRONTEND_BASE_PATH}/datasets?persistentId=${dataset.persistentId}`) + cy.wait(1500) + + cy.findByText('Files').should('exist') + cy.findByRole('button', { name: 'Access Dataset' }).should('exist').click({ force: true }) + cy.findByRole('button', { name: /Original Format ZIP/ }) + .should('exist') + .click({ force: true }) + + cy.findByRole('dialog').should('be.visible') + cy.findByText('Custom Dataset Terms').should('exist') + // Guestbook fields should not be visible since there is no guestbook + cy.findByLabelText(/name/i).should('not.exist') + cy.findByLabelText(/email/i).should('not.exist') + }) + }) + + it('downloads the dataset directly for editors even when custom terms exist without a guestbook', () => { + cy.wrap( + DatasetHelper.createWithFiles(FileHelper.createMany(2)).then(async (dataset) => { + await DatasetHelper.setCustomTermsOfUse(dataset.id, { + termsOfUse: 'These are custom terms of use for testing' + }) + await DatasetHelper.publish(dataset.persistentId) + return dataset + }) + ).then((dataset) => { + cy.visit(`${FRONTEND_BASE_PATH}/datasets?persistentId=${dataset.persistentId}`) + cy.wait(1500) + + cy.findByText('Files').should('exist') + cy.window().then((window) => { + cy.stub(window.HTMLAnchorElement.prototype, 'click').as('anchorClick') + }) + cy.findByRole('button', { name: 'Access Dataset' }).should('exist').click({ force: true }) + cy.findByRole('button', { name: /Original Format ZIP/ }) + .should('exist') + .click({ force: true }) + + cy.get('@anchorClick').should('have.been.calledOnce') + cy.findByRole('dialog').should('not.exist') + cy.findByText('Your download has started.').should('exist') + }) + }) + it('downloads a file', () => { cy.wrap( DatasetHelper.createWithFiles(FileHelper.createMany(1)).then((dataset) => @@ -850,7 +939,7 @@ describe('Dataset', () => { .its('persistentId') .then((persistentId: string) => { cy.wait(1500) // Wait for the dataset to be published - cy.visit(`/spa/datasets?persistentId=${persistentId}`) + cy.visit(`${FRONTEND_BASE_PATH}/datasets?persistentId=${persistentId}`) cy.wait(1500) // Wait for the page to load cy.findByText('Files').should('exist') @@ -876,7 +965,7 @@ describe('Dataset', () => { .its('persistentId') .then((persistentId: string) => { cy.wait(1500) // Wait for the page to load - cy.visit(`/spa/datasets?persistentId=${persistentId}`) + cy.visit(`${FRONTEND_BASE_PATH}/datasets?persistentId=${persistentId}`) cy.wait(1500) // Wait for the page to load cy.findByText('Files').should('exist') diff --git a/tests/e2e-integration/e2e/sections/file/File.spec.tsx b/tests/e2e-integration/e2e/sections/file/File.spec.tsx index 94df5d70e..361e09976 100644 --- a/tests/e2e-integration/e2e/sections/file/File.spec.tsx +++ b/tests/e2e-integration/e2e/sections/file/File.spec.tsx @@ -5,6 +5,8 @@ import { FileHelper } from '../../../shared/files/FileHelper' import { GuestbookHelper } from '../../../shared/guestbooks/GuestbookHelper' import { faker } from '@faker-js/faker' +const FRONTEND_BASE_PATH = '/spa' + describe('File', () => { beforeEach(() => { TestsUtils.login().then((token) => { @@ -19,7 +21,7 @@ describe('File', () => { throw new Error('Expected created dataset to include a file') } - cy.visit(`/spa/files?id=${datasetResponse.file.id}`) + cy.visit(`${FRONTEND_BASE_PATH}/files?id=${datasetResponse.file.id}`) cy.findByRole('heading', { name: 'blob' }).should('exist') cy.findByText(DatasetLabelValue.DRAFT).should('exist') @@ -39,7 +41,7 @@ describe('File', () => { } TestsUtils.logout() - cy.visit(`/spa/files?id=${datasetResponse.file.id}`) + cy.visit(`${FRONTEND_BASE_PATH}/files?id=${datasetResponse.file.id}`) cy.findByRole('heading', { name: 'blob' }).should('exist') @@ -62,7 +64,7 @@ describe('File', () => { throw new Error('Expected created dataset to include a file') } - cy.visit(`/spa/files?id=${datasetResponse.file.id}`) + cy.visit(`${FRONTEND_BASE_PATH}/files?id=${datasetResponse.file.id}`) cy.wait(3000) cy.findByRole('tab', { name: 'Versions' }).should('exist').click({ force: true }) @@ -79,7 +81,7 @@ describe('File', () => { } TestsUtils.logout() - cy.visit(`/spa/files?id=${datasetResponse.file.id}`) + cy.visit(`${FRONTEND_BASE_PATH}/files?id=${datasetResponse.file.id}`) cy.findByTestId('not-found-page').should('exist') }) @@ -92,7 +94,7 @@ describe('File', () => { throw new Error('Expected created dataset to include a file') } - cy.visit(`/spa/files?id=${datasetResponse.file.id}&datasetVersion=1.0`) + cy.visit(`${FRONTEND_BASE_PATH}/files?id=${datasetResponse.file.id}&datasetVersion=1.0`) cy.findByRole('heading', { name: 'blob' }).should('exist') @@ -102,7 +104,7 @@ describe('File', () => { }) it('loads page not found when passing a wrong id', () => { - cy.visit(`/spa/files?id=wrong-id`) + cy.visit(`${FRONTEND_BASE_PATH}/files?id=wrong-id`) cy.findByTestId('not-found-page').should('exist') }) @@ -112,7 +114,7 @@ describe('File', () => { throw new Error('Expected created dataset to include a file') } - cy.visit(`/spa/files?id=${datasetResponse.file.id}`) + cy.visit(`${FRONTEND_BASE_PATH}/files?id=${datasetResponse.file.id}`) cy.findByText('Root').should('exist') cy.findByRole('link', { name: "Darwin's Finches" }).should('exist').click({ force: true }) @@ -140,7 +142,7 @@ describe('File', () => { }) ) .then((file) => { - cy.visit(`/spa/files?id=${file.id}`) + cy.visit(`${FRONTEND_BASE_PATH}/files?id=${file.id}`) cy.wait(1500) cy.window().then((window) => { @@ -180,7 +182,7 @@ describe('File', () => { ) .then((file) => { TestsUtils.logout() - cy.visit(`/spa/files?id=${file.id}`) + cy.visit(`${FRONTEND_BASE_PATH}/files?id=${file.id}`) cy.wait(1500) cy.findByRole('button', { name: 'Access File' }).as('accessButton') @@ -195,5 +197,78 @@ describe('File', () => { }) }) }) + + it('opens the custom terms modal for guests on the file page when custom terms exist without a guestbook', () => { + cy.wrap(DatasetHelper.createWithFile(FileHelper.create())).then((dataset) => { + if (!dataset.file) { + throw new Error('Expected created dataset to include a file') + } + const file = dataset.file + + return cy + .wrap( + DatasetHelper.setCustomTermsOfUse(dataset.id, { + termsOfUse: 'File page custom terms for testing' + }).then(async () => { + await DatasetHelper.publish(dataset.persistentId) + return file + }) + ) + .then((file) => { + TestsUtils.logout() + cy.visit(`${FRONTEND_BASE_PATH}/files?id=${file.id}`) + cy.wait(1500) + + cy.findByRole('button', { name: 'Access File' }).as('accessButton') + cy.get('@accessButton').should('be.visible') + cy.wait(500) + cy.get('@accessButton').click() + cy.findByTestId('download-original-file').should('exist').click({ force: true }) + + cy.findByRole('dialog').should('be.visible') + cy.findByText('Custom Dataset Terms').should('exist') + // Guestbook fields should not be visible since there is no guestbook + cy.findByLabelText(/name/i).should('not.exist') + cy.findByLabelText(/email/i).should('not.exist') + }) + }) + }) + + it('downloads the file directly for editors on the file page when custom terms exist without a guestbook', () => { + cy.wrap(DatasetHelper.createWithFile(FileHelper.create())).then((dataset) => { + if (!dataset.file) { + throw new Error('Expected created dataset to include a file') + } + const file = dataset.file + + return cy + .wrap( + DatasetHelper.setCustomTermsOfUse(dataset.id, { + termsOfUse: 'File page custom terms for testing' + }).then(async () => { + await DatasetHelper.publish(dataset.persistentId) + return file + }) + ) + .then((file) => { + cy.visit(`${FRONTEND_BASE_PATH}/files?id=${file.id}`) + cy.wait(1500) + + cy.window().then((window) => { + cy.stub(window.HTMLAnchorElement.prototype, 'click').as('anchorClick') + }) + + cy.findByRole('button', { name: 'Access File' }).as('accessButton') + cy.get('@accessButton').should('be.visible') + cy.wait(500) + cy.get('@accessButton').click() + cy.findByTestId('download-original-file').should('exist').click({ force: true }) + + cy.get('@anchorClick').should('have.been.calledOnce') + cy.findByRole('dialog').should('not.exist') + cy.findByText('Your download has started.').should('exist') + }) + }) + }) }) }) diff --git a/tests/e2e-integration/shared/datasets/DatasetHelper.ts b/tests/e2e-integration/shared/datasets/DatasetHelper.ts index c6b8d3878..57e3647d4 100644 --- a/tests/e2e-integration/shared/datasets/DatasetHelper.ts +++ b/tests/e2e-integration/shared/datasets/DatasetHelper.ts @@ -262,6 +262,22 @@ export class DatasetHelper extends DataverseApiHelper { }>(`/datasets/${id}/lock/${reason}`, 'POST') } + static async setCustomTermsOfUse( + datasetId: string | number, + customTerms: { + termsOfUse: string + confidentialityDeclaration?: string + specialPermissions?: string + restrictions?: string + citationRequirements?: string + depositorRequirements?: string + conditions?: string + disclaimer?: string + } + ): Promise { + await this.request(`/datasets/${datasetId}/license`, 'PUT', { customTerms }) + } + static async createTemplate(collectionAlias?: string): Promise<{ id: number }> { if (collectionAlias == undefined) { collectionAlias = ':root' From 66f6812d40ad6ced845c981ccdaa60d279aa2d2a Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Tue, 14 Apr 2026 11:08:04 -0400 Subject: [PATCH 2/8] fix: e2e error --- tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx | 5 +++-- tests/e2e-integration/e2e/sections/file/File.spec.tsx | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx index bc65d204e..87a216945 100644 --- a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx +++ b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx @@ -868,13 +868,14 @@ describe('Dataset', () => { .click({ force: true }) cy.findByRole('dialog').should('be.visible') + cy.findByText(/Dataset Terms/i).should('exist') cy.findByLabelText(/name/i).should('be.enabled') cy.findByLabelText(/email/i).should('be.enabled') }) }) }) - it('opens the custom terms modal for guests when downloading a dataset with custom terms and no guestbook', () => { + it.only('opens the custom terms modal for guests when downloading a dataset with custom terms and no guestbook', () => { cy.wrap( DatasetHelper.createWithFiles(FileHelper.createMany(2)).then(async (dataset) => { await DatasetHelper.setCustomTermsOfUse(dataset.id, { @@ -895,7 +896,7 @@ describe('Dataset', () => { .click({ force: true }) cy.findByRole('dialog').should('be.visible') - cy.findByText('Custom Dataset Terms').should('exist') + cy.findByRole('dialog').find('.modal-title').should('contain.text', 'Dataset Terms') // Guestbook fields should not be visible since there is no guestbook cy.findByLabelText(/name/i).should('not.exist') cy.findByLabelText(/email/i).should('not.exist') diff --git a/tests/e2e-integration/e2e/sections/file/File.spec.tsx b/tests/e2e-integration/e2e/sections/file/File.spec.tsx index 361e09976..27370c77d 100644 --- a/tests/e2e-integration/e2e/sections/file/File.spec.tsx +++ b/tests/e2e-integration/e2e/sections/file/File.spec.tsx @@ -191,7 +191,7 @@ describe('File', () => { cy.get('@accessButton').click() cy.findByTestId('download-original-file').should('exist').click({ force: true }) - cy.findByRole('dialog').should('be.visible') + cy.findByRole('dialog').find('.modal-title').should('contain.text', 'Dataset Terms') cy.findByLabelText(/name/i).should('be.enabled') cy.findByLabelText(/email/i).should('be.enabled') }) From 36181ad2dae58b4e59e30b81a0d88dbcb2c6b9d2 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Tue, 14 Apr 2026 11:08:27 -0400 Subject: [PATCH 3/8] fix: copilot review --- .../access-file-menu/FileDownloadOptions.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sections/file/file-action-buttons/access-file-menu/FileDownloadOptions.tsx b/src/sections/file/file-action-buttons/access-file-menu/FileDownloadOptions.tsx index 258fbaaac..b9f793018 100644 --- a/src/sections/file/file-action-buttons/access-file-menu/FileDownloadOptions.tsx +++ b/src/sections/file/file-action-buttons/access-file-menu/FileDownloadOptions.tsx @@ -66,8 +66,9 @@ export function FileDownloadOptions({ const resolvedIsDraftDataset = isDraft ?? dataset?.version.publishingStatus === DatasetPublishingStatus.DRAFT const resolvedCanEdit = canEdit ?? dataset?.permissions.canUpdateDataset ?? false + const resolvedGuestbookId = guestbookId ?? dataset?.guestbookId + const hasGuestbook = resolvedGuestbookId !== undefined const bypassTermsGuard = resolvedIsDraftDataset || resolvedCanEdit - const hasGuestbook = guestbookId !== undefined const hasNonDefaultLicense = resolvedDatasetLicense !== undefined && resolvedDatasetLicense.name !== defaultLicense.name const hasCustomTerms = resolvedDatasetCustomTerms !== undefined @@ -108,7 +109,7 @@ export function FileDownloadOptions({ {shouldShowModal && showDownloadWithTermsAndGuestbookModal && ( Date: Tue, 14 Apr 2026 11:29:17 -0400 Subject: [PATCH 4/8] fix: remove .only in test --- tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx index 87a216945..0eca955d6 100644 --- a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx +++ b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx @@ -875,7 +875,7 @@ describe('Dataset', () => { }) }) - it.only('opens the custom terms modal for guests when downloading a dataset with custom terms and no guestbook', () => { + it('opens the custom terms modal for guests when downloading a dataset with custom terms and no guestbook', () => { cy.wrap( DatasetHelper.createWithFiles(FileHelper.createMany(2)).then(async (dataset) => { await DatasetHelper.setCustomTermsOfUse(dataset.id, { From 1e97acd8ebb5ea1bd5570e4de22f8567c5974234 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Tue, 14 Apr 2026 12:34:27 -0400 Subject: [PATCH 5/8] fix: e2e error --- tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx index 0eca955d6..16e6f9b9a 100644 --- a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx +++ b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx @@ -845,7 +845,7 @@ describe('Dataset', () => { }) }) - it('opens the guestbook modal for guests when downloading a dataset with an assigned guestbook', () => { + it.only('opens the guestbook modal for guests when downloading a dataset with an assigned guestbook', () => { const guestbookName = `Guestbook ${faker.datatype.uuid()}` cy.wrap(DatasetHelper.createWithFiles(FileHelper.createMany(2))).then((dataset) => { @@ -868,7 +868,7 @@ describe('Dataset', () => { .click({ force: true }) cy.findByRole('dialog').should('be.visible') - cy.findByText(/Dataset Terms/i).should('exist') + cy.findByRole('dialog').find('.modal-title').should('contain.text', 'Dataset Terms') cy.findByLabelText(/name/i).should('be.enabled') cy.findByLabelText(/email/i).should('be.enabled') }) From f955f58275ccbdd67794349841b55eb3c455f638 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Wed, 15 Apr 2026 10:08:35 -0400 Subject: [PATCH 6/8] fix: reviews, and add storybook --- .../DownloadWithTermsAndGuestbookModal.tsx | 6 +- .../GuestbookCollectForm.tsx | 109 +++++++++--------- ...loadWithTermsAndGuestbookModal.stories.tsx | 26 +++++ 3 files changed, 87 insertions(+), 54 deletions(-) diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/DownloadWithTermsAndGuestbookModal.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/DownloadWithTermsAndGuestbookModal.tsx index 496eddc52..1e873c074 100644 --- a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/DownloadWithTermsAndGuestbookModal.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/DownloadWithTermsAndGuestbookModal.tsx @@ -17,7 +17,7 @@ import { GuestbookAnswerDTO, GuestbookResponseDTO } from '@/access/domain/repositories/AccessRepository' -import { downloadFromSignedUrl } from '@/shared/helpers/DownloadHelper' +import { downloadFromSignedUrl, EMPTY_GUESTBOOK_RESPONSE } from '@/shared/helpers/DownloadHelper' import { FileDownloadMode } from '@/files/domain/models/FileMetadata' interface DownloadWithTermsAndGuestbookModalProps { @@ -179,6 +179,10 @@ export function DownloadWithTermsAndGuestbookModal({ } const buildGuestbookResponse = (): GuestbookResponseDTO => { + if (!guestbook) { + return EMPTY_GUESTBOOK_RESPONSE + } + const customQuestionAnswers = customQuestions.reduce( (answers, question, index) => { const fieldName = getGuestbookCustomQuestionFieldName(question, index) diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/GuestbookCollectForm.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/GuestbookCollectForm.tsx index 0acfbff3c..38cc99a5e 100644 --- a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/GuestbookCollectForm.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/GuestbookCollectForm.tsx @@ -176,66 +176,69 @@ export function GuestbookCollectForm({ - {guestbook && - accountFieldKeys.map((accountFieldKey) => { - const fieldLabel = tGuestbooks(`create.fields.dataCollected.options.${accountFieldKey}`) - const isRequired = isAccountFieldRequired(accountFieldKey) - const isIdentityField = accountFieldKey === 'name' || accountFieldKey === 'email' - const shouldDisableInput = shouldLockIdentityFields && isIdentityField - const invalid = hasAttemptedAccept && accountFieldErrors[accountFieldKey] !== null + {guestbook && ( + <> + {accountFieldKeys.map((accountFieldKey) => { + const fieldLabel = tGuestbooks(`create.fields.dataCollected.options.${accountFieldKey}`) + const isRequired = isAccountFieldRequired(accountFieldKey) + const isIdentityField = accountFieldKey === 'name' || accountFieldKey === 'email' + const shouldDisableInput = shouldLockIdentityFields && isIdentityField + const invalid = hasAttemptedAccept && accountFieldErrors[accountFieldKey] !== null + + return ( + + + + {fieldLabel} + {isRequired && *} + + + + + onFieldChange(accountFieldKey, (event.target as HTMLInputElement).value) + } + isInvalid={invalid} + disabled={shouldDisableInput} + aria-required={isRequired} + /> + + {invalid ? accountFieldErrors[accountFieldKey] : undefined} + + + + ) + })} - return ( - + {customQuestions.length > 0 && ( + - {fieldLabel} - {isRequired && *} + {tFiles('actions.optionsMenu.guestbookCollectModal.additionalQuestions')} - - - onFieldChange(accountFieldKey, (event.target as HTMLInputElement).value) - } - isInvalid={invalid} - disabled={shouldDisableInput} - aria-required={isRequired} - /> - - {invalid ? accountFieldErrors[accountFieldKey] : undefined} - + + {customQuestions.map((question, index) => ( +
+ + {renderCustomQuestionField(question, index)} +
+ ))}
- ) - })} - - {guestbook && customQuestions.length > 0 && ( - - - - {tFiles('actions.optionsMenu.guestbookCollectModal.additionalQuestions')} - - - - {customQuestions.map((question, index) => ( -
- - {renderCustomQuestionField(question, index)} -
- ))} - -
+ )} + )} ) diff --git a/src/stories/guestbooks/guestbook-applied-modal/DownloadWithTermsAndGuestbookModal.stories.tsx b/src/stories/guestbooks/guestbook-applied-modal/DownloadWithTermsAndGuestbookModal.stories.tsx index 8cea38094..1d1dfb810 100644 --- a/src/stories/guestbooks/guestbook-applied-modal/DownloadWithTermsAndGuestbookModal.stories.tsx +++ b/src/stories/guestbooks/guestbook-applied-modal/DownloadWithTermsAndGuestbookModal.stories.tsx @@ -68,3 +68,29 @@ export const MultipleFiles: Story = { guestbookId: 3 } } + +export const CustomTermsOnly: Story = { + args: { + show: true, + handleClose: () => {}, + fileId: 123, + datasetCustomTerms: { + termsOfUse: 'These are custom terms of use for this dataset.', + confidentialityDeclaration: 'All data must be kept confidential.', + restrictions: 'Do not redistribute without permission.' + } + } +} + +export const NonDefaultLicenseOnly: Story = { + args: { + show: true, + handleClose: () => {}, + fileId: 123, + datasetLicense: { + name: 'CC BY 4.0', + uri: 'https://creativecommons.org/licenses/by/4.0', + iconUri: 'https://licensebuttons.net/l/by/4.0/88x31.png' + } + } +} From 7f6a9a99becc12761cca4e3e26d5218de28510a4 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Wed, 15 Apr 2026 10:37:12 -0400 Subject: [PATCH 7/8] fix: remove .only in test --- tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx index 16e6f9b9a..47039f46a 100644 --- a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx +++ b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx @@ -845,7 +845,7 @@ describe('Dataset', () => { }) }) - it.only('opens the guestbook modal for guests when downloading a dataset with an assigned guestbook', () => { + it('opens the guestbook modal for guests when downloading a dataset with an assigned guestbook', () => { const guestbookName = `Guestbook ${faker.datatype.uuid()}` cy.wrap(DatasetHelper.createWithFiles(FileHelper.createMany(2))).then((dataset) => { From 521a4ffdee92af71c52541f4169d204ebdc9b175 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Fri, 17 Apr 2026 15:02:26 -0400 Subject: [PATCH 8/8] fix: e2e errors --- dev-env/shib-dev-env/keycloak/test-realm.json | 2 +- .../file-options-menu/GuestbookCollectForm.tsx | 2 +- .../guestbook/DownloadWithTermsAndGuestbookModal.spec.tsx | 2 +- tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx | 1 - tests/e2e-integration/e2e/sections/file/File.spec.tsx | 2 -- 5 files changed, 3 insertions(+), 6 deletions(-) diff --git a/dev-env/shib-dev-env/keycloak/test-realm.json b/dev-env/shib-dev-env/keycloak/test-realm.json index ea5a07378..0b53d51db 100644 --- a/dev-env/shib-dev-env/keycloak/test-realm.json +++ b/dev-env/shib-dev-env/keycloak/test-realm.json @@ -940,7 +940,7 @@ "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "redirectUris": [ - "https://localhost/spa/*" + "https://localhost/modern/*" ], "webOrigins": [ "+" diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/GuestbookCollectForm.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/GuestbookCollectForm.tsx index 38cc99a5e..be72aba58 100644 --- a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/GuestbookCollectForm.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/GuestbookCollectForm.tsx @@ -54,7 +54,7 @@ export function GuestbookCollectForm({ [guestbook?.customQuestions] ) const customTermsHref = datasetPersistentId - ? `/spa${Route.DATASETS}?${QueryParamKey.PERSISTENT_ID}=${encodeURIComponent( + ? `/modern${Route.DATASETS}?${QueryParamKey.PERSISTENT_ID}=${encodeURIComponent( datasetPersistentId )}&${QueryParamKey.TAB}=terms&termsTab=guestbook` : undefined diff --git a/tests/component/sections/dataset/dataset-files/guestbook/DownloadWithTermsAndGuestbookModal.spec.tsx b/tests/component/sections/dataset/dataset-files/guestbook/DownloadWithTermsAndGuestbookModal.spec.tsx index 121d918d7..66095d974 100644 --- a/tests/component/sections/dataset/dataset-files/guestbook/DownloadWithTermsAndGuestbookModal.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/guestbook/DownloadWithTermsAndGuestbookModal.spec.tsx @@ -233,7 +233,7 @@ describe('DownloadWithTermsAndGuestbookModal', () => { cy.findByRole('link', { name: 'Custom Dataset Terms' }).should( 'have.attr', 'href', - '/spa/datasets?persistentId=doi%3A10.5072%2FFK2%2FFILEPAGE&tab=terms&termsTab=guestbook' + '/modern/datasets?persistentId=doi%3A10.5072%2FFK2%2FFILEPAGE&tab=terms&termsTab=guestbook' ) }) diff --git a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx index f86ba7253..1bba11036 100644 --- a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx +++ b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx @@ -16,7 +16,6 @@ type Dataset = { datasetVersion: { metadataBlocks: { citation: { fields: { value: string }[] } } } } const DRAFT_PARAM = DatasetNonNumericVersionSearchParam.DRAFT -const FRONTEND_BASE_PATH = '/spa' describe('Dataset', () => { beforeEach(() => { diff --git a/tests/e2e-integration/e2e/sections/file/File.spec.tsx b/tests/e2e-integration/e2e/sections/file/File.spec.tsx index 31e985426..38276a6f6 100644 --- a/tests/e2e-integration/e2e/sections/file/File.spec.tsx +++ b/tests/e2e-integration/e2e/sections/file/File.spec.tsx @@ -6,8 +6,6 @@ import { FileHelper } from '../../../shared/files/FileHelper' import { GuestbookHelper } from '../../../shared/guestbooks/GuestbookHelper' import { faker } from '@faker-js/faker' -const FRONTEND_BASE_PATH = '/spa' - describe('File', () => { beforeEach(() => { TestsUtils.login().then((token) => {