Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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>(
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
Expand All @@ -79,7 +84,7 @@ export function AccessDatasetMenu({
) => {
event.preventDefault()
setSelectedDownloadFormat(mode)
setShowDownloadWithGuestbookModal(true)
setShowDownloadWithTermsAndGuestbookModal(true)
}

return (
Expand All @@ -96,15 +101,15 @@ export function AccessDatasetMenu({
datasetNumericId={datasetNumericId}
hasOneTabularFileAtLeast={hasOneTabularFileAtLeast}
fileDownloadSizes={fileDownloadSizes}
hasGuestbook={hasGuestbook}
requiresTermsOrGuestbook={shouldShowModal}
onDownloadWithGuestbook={handleDownloadWithGuestbook}
/>
<DatasetExploreOptions persistentId={persistentId} />
</DropdownButton>
{hasGuestbook && showDownloadWithGuestbookModal && (
<DownloadWithGuestbookModal
show={showDownloadWithGuestbookModal}
handleClose={() => setShowDownloadWithGuestbookModal(false)}
{shouldShowModal && showDownloadWithTermsAndGuestbookModal && (
<DownloadWithTermsAndGuestbookModal
show={showDownloadWithTermsAndGuestbookModal}
handleClose={() => 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}
Expand All @@ -121,15 +126,15 @@ interface DatasetDownloadOptionsProps {
datasetNumericId?: number | string
hasOneTabularFileAtLeast: boolean
fileDownloadSizes: FileDownloadSize[]
hasGuestbook: boolean
requiresTermsOrGuestbook: boolean
onDownloadWithGuestbook: (event: React.MouseEvent<HTMLElement>, mode: FileDownloadMode) => void
}

const DatasetDownloadOptions = ({
datasetNumericId,
hasOneTabularFileAtLeast,
fileDownloadSizes,
hasGuestbook,
requiresTermsOrGuestbook,
onDownloadWithGuestbook
}: DatasetDownloadOptionsProps) => {
const { t } = useTranslation('dataset')
Expand All @@ -140,7 +145,7 @@ const DatasetDownloadOptions = ({
event: React.MouseEvent<HTMLElement>,
mode: FileDownloadMode
): void => {
if (hasGuestbook) {
if (requiresTermsOrGuestbook) {
onDownloadWithGuestbook(event, mode)
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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>(
FileDownloadMode.ORIGINAL
)
Expand All @@ -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<HTMLElement>, downloadMode: FileDownloadMode) => {
if (fileSelectionCount === SELECTED_FILES_EMPTY) {
Expand All @@ -55,10 +61,10 @@ export function DownloadFilesButton({ files, fileSelection }: DownloadFilesButto
return
}

if (hasGuestbook) {
if (shouldShowModal) {
event.preventDefault()
setSelectedDownloadFormat(downloadMode)
setShowDownloadWithGuestbookModal(true)
setShowDownloadWithTermsAndGuestbookModal(true)
return
}

Expand Down Expand Up @@ -101,15 +107,17 @@ export function DownloadFilesButton({ files, fileSelection }: DownloadFilesButto
show={showNoFilesSelectedModal}
handleClose={() => setShowNoFilesSelectedModal(false)}
/>
{hasGuestbook && showDownloadWithGuestbookModal && (
<DownloadWithGuestbookModal
{shouldShowModal && showDownloadWithTermsAndGuestbookModal && (
<DownloadWithTermsAndGuestbookModal
fileIds={fileIdsForGuestbookSubmission}
datasetId={allFilesSelected ? dataset.id : undefined}
guestbookId={dataset.guestbookId}
format={selectedDownloadFormat}
datasetPersistentId={dataset.persistentId}
show={showDownloadWithGuestbookModal}
handleClose={() => setShowDownloadWithGuestbookModal(false)}
datasetLicense={dataset.license}
datasetCustomTerms={dataset.termsOfUse?.customTerms}
show={showDownloadWithTermsAndGuestbookModal}
handleClose={() => setShowDownloadWithTermsAndGuestbookModal(false)}
/>
)}
</>
Expand Down Expand Up @@ -140,27 +148,15 @@ export function DownloadFilesButton({ files, fileSelection }: DownloadFilesButto
// no tabular file content
return (
<>
{hasGuestbook ? (
<Button
id="download-files"
variant="secondary"
icon={<Download className={styles.icon} />}
aria-label={t('actions.downloadFiles.title')}
withSpacing
onClick={(event) => onClick(event, FileDownloadMode.ORIGINAL)}>
{dropdownButtonTitle}
</Button>
) : (
<Button
id="download-files"
variant="secondary"
icon={<Download className={styles.icon} />}
aria-label={t('actions.downloadFiles.title')}
withSpacing
onClick={(event) => onClick(event, FileDownloadMode.ORIGINAL)}>
{dropdownButtonTitle}
</Button>
)}
<Button
id="download-files"
variant="secondary"
icon={<Download className={styles.icon} />}
aria-label={t('actions.downloadFiles.title')}
withSpacing
onClick={(event) => onClick(event, FileDownloadMode.ORIGINAL)}>
{dropdownButtonTitle}
</Button>

{downloadFeedbackModals}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<number>
format?: string | FileDownloadMode
Expand All @@ -34,7 +34,7 @@ interface DownloadWithGuestbookModalProps {
}

type GuestbookFormValues = Record<string, string>
export function DownloadWithGuestbookModal({
export function DownloadWithTermsAndGuestbookModal({
fileId,
fileIds,
format,
Expand All @@ -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<GuestbookFormValues>({})
const { guestbook, isLoadingGuestbook, errorGetGuestbook } = useGetGuestbookById({
guestbookRepository,
guestbookId,
enabled: show
enabled: show && hasGuestbook
})
const accountFieldKeys = useMemo(() => ['name', 'email', 'institution', 'position'], [])

Expand Down Expand Up @@ -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 ? <Spinner variant="light" animation="border" size="sm" /> : null}{' '}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -176,45 +176,46 @@ export function GuestbookCollectForm({
</Form.Group>
<CustomTerms customTerms={customTerms} />

{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 &&
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two code blocks that have the same guestbook && condition, can that logic be moved up so it is one code block with that condition?

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 (
<Form.Group
key={accountFieldKey}
as={Row}
className={styles['form-row']}
controlId={`guestbook-${accountFieldKey}`}>
<Col sm={3}>
<Form.Group.Label className={styles['row-label']}>
{fieldLabel}
{isRequired && <span className={styles.required}>*</span>}
</Form.Group.Label>
</Col>
<Col sm={9}>
<Form.Group.Input
type="text"
value={formValues[accountFieldKey] ?? ''}
onChange={(event) =>
onFieldChange(accountFieldKey, (event.target as HTMLInputElement).value)
}
isInvalid={invalid}
disabled={shouldDisableInput}
aria-required={isRequired}
/>
<Form.Group.Feedback type="invalid">
{invalid ? accountFieldErrors[accountFieldKey] : undefined}
</Form.Group.Feedback>
</Col>
</Form.Group>
)
})}
return (
<Form.Group
key={accountFieldKey}
as={Row}
className={styles['form-row']}
controlId={`guestbook-${accountFieldKey}`}>
<Col sm={3}>
<Form.Group.Label className={styles['row-label']}>
{fieldLabel}
{isRequired && <span className={styles.required}>*</span>}
</Form.Group.Label>
</Col>
<Col sm={9}>
<Form.Group.Input
type="text"
value={formValues[accountFieldKey] ?? ''}
onChange={(event) =>
onFieldChange(accountFieldKey, (event.target as HTMLInputElement).value)
}
isInvalid={invalid}
disabled={shouldDisableInput}
aria-required={isRequired}
/>
<Form.Group.Feedback type="invalid">
{invalid ? accountFieldErrors[accountFieldKey] : undefined}
</Form.Group.Feedback>
</Col>
</Form.Group>
)
})}

{customQuestions.length > 0 && (
{guestbook && customQuestions.length > 0 && (
<Form.Group as={Row} className={styles['form-row']}>
<Col sm={3}>
<Form.Group.Label className={styles['row-label']}>
Expand Down
Loading
Loading