Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion dev-env/shib-dev-env/keycloak/test-realm.json
Original file line number Diff line number Diff line change
Expand Up @@ -940,7 +940,7 @@
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": [
"https://localhost/spa/*"
"https://localhost/modern/*"
],
"webOrigins": [
"+"
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 @@ -17,10 +17,10 @@ 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 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 @@ -178,6 +179,10 @@ export function DownloadWithGuestbookModal({
}

const buildGuestbookResponse = (): GuestbookResponseDTO => {
if (!guestbook) {
return EMPTY_GUESTBOOK_RESPONSE
}

const customQuestionAnswers = customQuestions.reduce<GuestbookAnswerDTO[]>(
(answers, question, index) => {
const fieldName = getGuestbookCustomQuestionFieldName(question, index)
Expand Down Expand Up @@ -275,12 +280,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
Loading
Loading