diff --git a/public/locales/en/file.json b/public/locales/en/file.json index 2b166abee..f20bf0fa8 100644 --- a/public/locales/en/file.json +++ b/public/locales/en/file.json @@ -160,6 +160,7 @@ "getCategoriesError": "Something went wrong fetching available categories. Try again later.", "previewTab": { "openInNewWindow": "Open in New Window", - "defaultLoadingToolError": "Something went wrong loading the external tool. Try again later." + "defaultLoadingToolError": "Something went wrong loading the external tool. Try again later.", + "termsRequired": "Accept the dataset terms or guestbooks before previewing this file." } } diff --git a/public/locales/es/file.json b/public/locales/es/file.json index 6fb27e80d..1b4e28533 100644 --- a/public/locales/es/file.json +++ b/public/locales/es/file.json @@ -160,6 +160,7 @@ "getCategoriesError": "Algo salió mal al obtener las categorías disponibles. Intenta nuevamente más tarde.", "previewTab": { "openInNewWindow": "Abrir en una nueva ventana", - "defaultLoadingToolError": "Algo salió mal al cargar la herramienta externa. Intenta nuevamente más tarde." + "defaultLoadingToolError": "Algo salió mal al cargar la herramienta externa. Intenta nuevamente más tarde.", + "termsRequired": "Acepta los términos del dataset antes de previsualizar este fichero." } } 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 1e873c074..6512cd243 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 @@ -31,6 +31,7 @@ interface DownloadWithTermsAndGuestbookModalProps { datasetCustomTerms?: CustomTermsModel show: boolean handleClose: () => void + onAccept?: () => void } type GuestbookFormValues = Record @@ -44,7 +45,8 @@ export function DownloadWithTermsAndGuestbookModal({ datasetLicense, datasetCustomTerms, show, - handleClose + handleClose, + onAccept }: DownloadWithTermsAndGuestbookModalProps) { const { t: tFiles } = useTranslation('files') const { t: tDataset } = useTranslation('dataset') @@ -226,7 +228,8 @@ export function DownloadWithTermsAndGuestbookModal({ format, handleClose, accessRepository, - downloadFromSignedUrl + downloadFromSignedUrl, + onSubmitSuccess: onAccept }) useEffect(() => { 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 0dcecb195..ede74d90d 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 @@ -20,6 +20,7 @@ interface UseGuestbookCollectSubmissionProps { handleClose: () => void accessRepository: AccessRepository downloadFromSignedUrl: (signedUrl: string) => Promise + onSubmitSuccess?: () => void } interface HandleSubmitProps { @@ -34,7 +35,8 @@ export const useGuestbookCollectSubmission = ({ format, handleClose, accessRepository, - downloadFromSignedUrl + downloadFromSignedUrl, + onSubmitSuccess }: UseGuestbookCollectSubmissionProps) => { const { t: tFiles } = useTranslation('files') const [hasAttemptedAccept, setHasAttemptedAccept] = useState(false) @@ -108,6 +110,12 @@ export const useGuestbookCollectSubmission = ({ setIsSubmittingGuestbook(false) } + if (signedUrl && onSubmitSuccess) { + handleModalClose() + onSubmitSuccess() + return + } + if (signedUrl) { void downloadFromSignedUrl(signedUrl) .then(() => { @@ -132,6 +140,7 @@ export const useGuestbookCollectSubmission = ({ fileIds, format, handleModalClose, + onSubmitSuccess, tFiles ] ) diff --git a/src/sections/edit-dataset-terms/datasetTermsNavigation.ts b/src/sections/edit-dataset-terms/datasetTermsNavigation.ts new file mode 100644 index 000000000..f518b213e --- /dev/null +++ b/src/sections/edit-dataset-terms/datasetTermsNavigation.ts @@ -0,0 +1,27 @@ +import { + Dataset, + DatasetNonNumericVersionSearchParam, + DatasetPublishingStatus +} from '@/dataset/domain/models/Dataset' +import { QueryParamKey, Route } from '@/sections/Route.enum' + +export const buildDatasetTermsReturnUrl = (dataset: Dataset): string => { + const searchParams = new URLSearchParams() + searchParams.set(QueryParamKey.PERSISTENT_ID, dataset.persistentId) + + if (dataset.version.publishingStatus === DatasetPublishingStatus.DRAFT) { + searchParams.set(QueryParamKey.VERSION, DatasetNonNumericVersionSearchParam.DRAFT) + } else { + searchParams.set(QueryParamKey.VERSION, dataset.version.number.toString()) + } + + return `${Route.DATASETS}?${searchParams.toString()}` +} + +export const buildDatasetDraftReturnUrl = (dataset: Dataset): string => { + const searchParams = new URLSearchParams() + searchParams.set(QueryParamKey.PERSISTENT_ID, dataset.persistentId) + searchParams.set(QueryParamKey.VERSION, DatasetNonNumericVersionSearchParam.DRAFT) + + return `${Route.DATASETS}?${searchParams.toString()}` +} diff --git a/src/sections/edit-dataset-terms/edit-guestbook/EditGuestbook.tsx b/src/sections/edit-dataset-terms/edit-guestbook/EditGuestbook.tsx index d91e7185b..ef3126ded 100644 --- a/src/sections/edit-dataset-terms/edit-guestbook/EditGuestbook.tsx +++ b/src/sections/edit-dataset-terms/edit-guestbook/EditGuestbook.tsx @@ -5,16 +5,12 @@ import { useNavigate } from 'react-router-dom' import { toast } from 'react-toastify' import { Guestbook } from '@/guestbooks/domain/models/Guestbook' import { GuestbookRepository } from '@/guestbooks/domain/repositories/GuestbookRepository' -import { - DatasetNonNumericVersionSearchParam, - DatasetPublishingStatus -} from '@/dataset/domain/models/Dataset' import { useGetGuestbooksByCollectionId } from '@/sections/guestbooks/useGetGuestbooksByCollectionId' -import { QueryParamKey, Route } from '@/sections/Route.enum' import { useAssignDatasetGuestbook } from './useAssignDatasetGuestbook' import { useRemoveDatasetGuestbook } from './useRemoveDatasetGuestbook' import { useDataset } from '../../dataset/DatasetContext' import { PreviewGuestbookModal } from '@/sections/guestbooks/preview-modal/PreviewGuestbookModal' +import { buildDatasetDraftReturnUrl, buildDatasetTermsReturnUrl } from '../datasetTermsNavigation' import styles from './EditGuestbook.module.scss' interface EditGuestbookProps { @@ -40,16 +36,13 @@ export function EditGuestbook({ const navigateToDatasetView = useCallback(() => { if (!dataset) return - const searchParams = new URLSearchParams() - searchParams.set(QueryParamKey.PERSISTENT_ID, dataset.persistentId) + navigate(buildDatasetTermsReturnUrl(dataset)) + }, [dataset, navigate]) - if (dataset.version.publishingStatus === DatasetPublishingStatus.DRAFT) { - searchParams.set(QueryParamKey.VERSION, DatasetNonNumericVersionSearchParam.DRAFT) - } else { - searchParams.set(QueryParamKey.VERSION, dataset.version.number.toString()) - } + const navigateToDatasetDraftView = useCallback(() => { + if (!dataset) return - navigate(`${Route.DATASETS}?${searchParams.toString()}`) + navigate(buildDatasetDraftReturnUrl(dataset)) }, [dataset, navigate]) const handleCancel = () => { @@ -70,7 +63,7 @@ export function EditGuestbook({ onSuccessfulAssignDatasetGuestbook: () => { toast.success(t('alerts.termsUpdated.alertText')) refreshDataset() - navigateToDatasetView() + navigateToDatasetDraftView() } }) const { @@ -82,7 +75,7 @@ export function EditGuestbook({ onSuccessfulRemoveDatasetGuestbook: () => { toast.success(t('alerts.termsUpdated.alertText')) refreshDataset() - navigateToDatasetView() + navigateToDatasetDraftView() } }) diff --git a/src/sections/edit-dataset-terms/edit-license-and-terms/EditLicenseAndTerms.tsx b/src/sections/edit-dataset-terms/edit-license-and-terms/EditLicenseAndTerms.tsx index 02abc4d56..17af24cbd 100644 --- a/src/sections/edit-dataset-terms/edit-license-and-terms/EditLicenseAndTerms.tsx +++ b/src/sections/edit-dataset-terms/edit-license-and-terms/EditLicenseAndTerms.tsx @@ -10,11 +10,7 @@ import { useGetLicenses } from './useGetLicenses' import { DatasetRepository } from '@/dataset/domain/repositories/DatasetRepository' import { useDataset } from '../../dataset/DatasetContext' import { useUpdateDatasetLicense } from './useUpdateDatasetLicense' -import { Route, QueryParamKey } from '../../Route.enum' -import { - DatasetNonNumericVersionSearchParam, - DatasetPublishingStatus -} from '../../../dataset/domain/models/Dataset' +import { buildDatasetDraftReturnUrl, buildDatasetTermsReturnUrl } from '../datasetTermsNavigation' import styles from './EditLicenseAndTerms.module.scss' const CUSTOM_LICENSE_VALUE = 'CUSTOM' as const @@ -50,16 +46,13 @@ export function EditLicenseAndTerms({ const navigateToDatasetView = useCallback(() => { if (!dataset) return - const searchParams = new URLSearchParams() - searchParams.set(QueryParamKey.PERSISTENT_ID, dataset.persistentId) + navigate(buildDatasetTermsReturnUrl(dataset)) + }, [dataset, navigate]) - if (dataset.version.publishingStatus === DatasetPublishingStatus.DRAFT) { - searchParams.set(QueryParamKey.VERSION, DatasetNonNumericVersionSearchParam.DRAFT) - } else { - searchParams.set(QueryParamKey.VERSION, dataset.version.number.toString()) - } + const navigateToDatasetDraftView = useCallback(() => { + if (!dataset) return - navigate(`${Route.DATASETS}?${searchParams.toString()}`) + navigate(buildDatasetDraftReturnUrl(dataset)) }, [dataset, navigate]) const { handleUpdateLicense, isLoading, error } = useUpdateDatasetLicense({ @@ -67,7 +60,7 @@ export function EditLicenseAndTerms({ onSuccessfulUpdateLicense: () => { toast.success(t('alerts.licenseUpdated.alertText')) refreshDataset() - navigateToDatasetView() + navigateToDatasetDraftView() } }) diff --git a/src/sections/edit-dataset-terms/edit-terms-of-access/EditTermsOfAccess.tsx b/src/sections/edit-dataset-terms/edit-terms-of-access/EditTermsOfAccess.tsx index fb39ee1df..a6de26ae3 100644 --- a/src/sections/edit-dataset-terms/edit-terms-of-access/EditTermsOfAccess.tsx +++ b/src/sections/edit-dataset-terms/edit-terms-of-access/EditTermsOfAccess.tsx @@ -1,19 +1,15 @@ -import { useEffect, useMemo, useRef } from 'react' +import { useCallback, useEffect, useMemo, useRef } from 'react' import { useTranslation } from 'react-i18next' import { useForm, Controller, FormProvider, useWatch } from 'react-hook-form' import { toast } from 'react-toastify' import { Form, Row, Col, Button, Alert } from '@iqss/dataverse-design-system' import styles from '../edit-license-and-terms/EditLicenseAndTerms.module.scss' -import { - DatasetNonNumericVersionSearchParam, - DatasetPublishingStatus, - TermsOfAccess -} from '@/dataset/domain/models/Dataset' +import { TermsOfAccess } from '@/dataset/domain/models/Dataset' import { DatasetRepository } from '@/dataset/domain/repositories/DatasetRepository' import { useDataset } from '../../dataset/DatasetContext' import { useUpdateTermsOfAccess } from './useUpdateTermsOfAccess' -import { QueryParamKey, Route } from '@/sections/Route.enum' import { useNavigate } from 'react-router-dom' +import { buildDatasetDraftReturnUrl, buildDatasetTermsReturnUrl } from '../datasetTermsNavigation' interface EditTermsOfAccessProps { datasetRepository: DatasetRepository @@ -43,12 +39,18 @@ export function EditTermsOfAccess({ const initialTermsOfAccess = (dataset?.termsOfUse.termsOfAccess as TermsOfAccess) ?? defaultTermsOfAccess const formContainerRef = useRef(null) + const navigateToDatasetDraftView = useCallback(() => { + if (!dataset) return + + navigate(buildDatasetDraftReturnUrl(dataset)) + }, [dataset, navigate]) const { handleUpdateTermsOfAccess, isLoading, error } = useUpdateTermsOfAccess({ datasetRepository, onSuccessfulUpdateTermsOfAccess: () => { toast.success(t('alerts.termsUpdated.alertText')) refreshDataset() + navigateToDatasetDraftView() } }) @@ -122,16 +124,7 @@ export function EditTermsOfAccess({ const handleCancel = () => { if (!dataset) return - const searchParams = new URLSearchParams() - searchParams.set(QueryParamKey.PERSISTENT_ID, dataset.persistentId) - - if (dataset.version.publishingStatus === DatasetPublishingStatus.DRAFT) { - searchParams.set(QueryParamKey.VERSION, DatasetNonNumericVersionSearchParam.DRAFT) - } else { - searchParams.set(QueryParamKey.VERSION, dataset.version.number.toString()) - } - - navigate(`${Route.DATASETS}?${searchParams.toString()}`) + navigate(buildDatasetTermsReturnUrl(dataset)) } return ( diff --git a/src/sections/file/file-action-buttons/access-file-menu/FileToolOptions.tsx b/src/sections/file/file-action-buttons/access-file-menu/FileToolOptions.tsx index 1b3adf353..b162f80f3 100644 --- a/src/sections/file/file-action-buttons/access-file-menu/FileToolOptions.tsx +++ b/src/sections/file/file-action-buttons/access-file-menu/FileToolOptions.tsx @@ -55,7 +55,7 @@ const FileToolOptions = ({ fileId, fileType, kind }: FileToolOptionsProps) => { - {tools.map((tool) => ( + {applicableTools.map((tool) => ( (null) const [fileExternalToolResolved, setFileExternalToolResolved] = useState(null) + const [showTermsAndGuestbookModal, setShowTermsAndGuestbookModal] = useState(false) + const [termsAndGuestbookAccepted, setTermsAndGuestbookAccepted] = useState(false) const moreThanOneTool = applicableTools.length > 1 + const bypassTermsGuard = + file.datasetVersion.publishingStatus === DatasetPublishingStatus.DRAFT || + file.permissions.canEditOwnerDataset + const hasGuestbook = file.guestbookId !== undefined + const hasCustomTerms = file.datasetCustomTerms !== undefined + const hasNonDefaultLicense = + file.datasetLicense !== undefined && file.datasetLicense.name !== defaultLicense.name + const requiresTermsAndGuestbook = + !bypassTermsGuard && (hasGuestbook || hasCustomTerms || hasNonDefaultLicense) + const isWaitingForTermsAndGuestbook = requiresTermsAndGuestbook && !termsAndGuestbookAccepted const handleToolSelect = (eventKey: string | null) => setToolIdSelected(Number(eventKey)) @@ -50,6 +64,10 @@ export const FileEmbeddedExternalTool = ({ // Loads the tool every time the tab is in view or the tool selection changes. useEffect(() => { if (!isInView) return + if (isWaitingForTermsAndGuestbook) { + setShowTermsAndGuestbookModal(true) + return + } const fetchFileExternalToolResolved = async () => { setIframeLoaded(false) @@ -78,10 +96,30 @@ export const FileEmbeddedExternalTool = ({ } void fetchFileExternalToolResolved() - }, [isInView, toolIdSelected, externalToolsRepository, file.id, t, i18n.languages]) + }, [ + isInView, + isWaitingForTermsAndGuestbook, + toolIdSelected, + externalToolsRepository, + file.id, + t, + i18n.languages + ]) return (
+ {requiresTermsAndGuestbook && showTermsAndGuestbookModal && ( + setShowTermsAndGuestbookModal(false)} + onAccept={() => setTermsAndGuestbookAccepted(true)} + /> + )}
{moreThanOneTool && ( )} {/* Keep overlay on top of the iframe while it loads to mask flickering */} -
- -
+ {!isWaitingForTermsAndGuestbook && ( +
+ +
+ )} + {isWaitingForTermsAndGuestbook && !showTermsAndGuestbookModal && ( + + {t('termsRequired')} + + )} {/* Show error message if fetching the tool URL fails or the iframe somehow fails. */} {errorLoadingTool && ( diff --git a/tests/component/sections/dataset/dataset-action-buttons/DatasetToolOptions.spec.tsx b/tests/component/sections/dataset/dataset-action-buttons/DatasetToolOptions.spec.tsx index c83b395a0..dc4d9cbbe 100644 --- a/tests/component/sections/dataset/dataset-action-buttons/DatasetToolOptions.spec.tsx +++ b/tests/component/sections/dataset/dataset-action-buttons/DatasetToolOptions.spec.tsx @@ -6,6 +6,7 @@ import { import { ExternalToolsProvider } from '@/shared/contexts/external-tools/ExternalToolsProvider' import { DatasetExternalToolResolvedMother } from '@tests/component/externalTools/domain/models/DatasetExternalToolResolvedMother' import { ExternalToolsMother } from '@tests/component/externalTools/domain/models/ExternalToolsMother' +import { ToolScope, ToolType } from '@/externalTools/domain/models/ExternalTool' const testExternalToolsRepository: ExternalToolsRepository = {} as ExternalToolsRepository @@ -53,6 +54,27 @@ describe('DatasetToolOptions', () => { cy.findByText('Dataset Explore Tool').should('exist') }) + it('renders only dataset explore tools for explore options', () => { + testExternalToolsRepository.getExternalTools = cy + .stub() + .resolves([ + testDatasetExploreTool, + testDatasetConfigureTool, + ExternalToolsMother.createFileExploreTool() + ]) + + cy.customMount( + + + + ) + + cy.findByText('Explore Options').should('exist') + cy.findByText('Dataset Explore Tool').should('exist') + cy.findByText('Dataset Configure Tool').should('not.exist') + cy.findByText('File Explore Tool').should('not.exist') + }) + it('renders nothing if there are no dataset explore tools', () => { testExternalToolsRepository.getExternalTools = cy.stub().resolves([]) cy.customMount( @@ -140,6 +162,77 @@ describe('DatasetToolOptions', () => { }) }) + it('calls getDatasetExternalToolResolved with the selected configure tool id', () => { + testExternalToolsRepository.getExternalTools = cy.stub().resolves([ + testDatasetConfigureTool, + { + ...testDatasetConfigureTool, + id: 55, + displayName: 'Second Dataset Configure Tool' + } + ]) + testExternalToolsRepository.getDatasetExternalToolResolved = cy + .stub() + .as('getDatasetExternalToolResolved') + .resolves( + DatasetExternalToolResolvedMother.create({ + toolUrlResolved: 'https://example.com/configure-tool' + }) + ) + + const fakeWindow = { + closed: false, + document: { title: '' }, + location: { href: '' }, + close: cy.stub().as('windowCloseStub') + } + + cy.window().then((win) => { + cy.stub(win, 'open').as('windowOpen').returns(fakeWindow) + }) + + cy.customMount( + + + + ) + + cy.findByText('Second Dataset Configure Tool').click() + + cy.get('@getDatasetExternalToolResolved') + .should('have.been.calledOnce') + .its('firstCall.args') + .then((args) => { + expect(args[0]).to.equal('doi:10.5072/FK2/CONFIGURE') + expect(args[1]).to.equal(55) + expect(args[2]).to.have.property('preview', false) + }) + }) + + it('does not render dataset tools that require unsupported requirements metadata', () => { + testExternalToolsRepository.getExternalTools = cy.stub().resolves([ + { + id: 77, + displayName: 'Dataset Explore Tool With Requirements', + description: 'Description for Dataset Explore Tool With Requirements', + scope: ToolScope.Dataset, + types: [ToolType.Explore], + requirements: { + auxFilesExist: [{ formatTag: 'DP', formatVersion: '1.0' }] + } + } + ]) + + cy.customMount( + + + + ) + + cy.findByText('Explore Options').should('not.exist') + cy.findByText('Dataset Explore Tool With Requirements').should('not.exist') + }) + it('shows an error toast if fetching the tool URL fails', () => { testExternalToolsRepository.getDatasetExternalToolResolved = cy .stub() diff --git a/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/FileTools.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/FileTools.spec.tsx index 9ff86f86a..274d6bd7f 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/FileTools.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/FileTools.spec.tsx @@ -5,6 +5,7 @@ import { ExternalToolsProvider } from '@/shared/contexts/external-tools/External import { ExternalToolsMother } from '@tests/component/externalTools/domain/models/ExternalToolsMother' import { FileMetadataMother } from '@tests/component/files/domain/models/FileMetadataMother' import { FilePreviewMother } from '@tests/component/files/domain/models/FilePreviewMother' +import { ToolScope, ToolType } from '@/externalTools/domain/models/ExternalTool' const testFilePreview = FilePreviewMother.createDefault() // text/plain file const testExternalToolsRepository: ExternalToolsRepository = {} as ExternalToolsRepository @@ -20,6 +21,15 @@ describe('FileTools', () => { }) it('renders external tool buttons when user can download the file and there are applicable tools', () => { + testExternalToolsRepository.getExternalTools = cy + .stub() + .resolves([ + ExternalToolsMother.createFilePreviewTool(), + ExternalToolsMother.createFileQueryTool(), + ExternalToolsMother.createFileExploreTool(), + ExternalToolsMother.createFileConfigureTool() + ]) + cy.customMount( @@ -45,6 +55,8 @@ describe('FileTools', () => { .and('include', `id=${testFilePreview.id}`) .and('include', `datasetVersion=${testFilePreview.datasetVersionNumber.toString()}`) .and('include', `${QueryParamKey.TOOL_TYPE}=query`) + + cy.findAllByRole('link').should('have.length', 2) }) it('does not render external tool buttons when user cannot download the file', () => { @@ -53,6 +65,9 @@ describe('FileTools', () => { ) + + cy.findByRole('link', { name: `Preview ${testFilePreview.name}` }).should('not.exist') + cy.findByRole('link', { name: `Query ${testFilePreview.name}` }).should('not.exist') }) it('does not render external tool buttons when there are no applicable tools for the file type', () => { @@ -67,5 +82,34 @@ describe('FileTools', () => { ) + + cy.findByRole('link', { name: `Preview ${fileWithoutApplicableTools.name}` }).should( + 'not.exist' + ) + cy.findByRole('link', { name: `Query ${fileWithoutApplicableTools.name}` }).should('not.exist') + }) + + it('does not render preview or query buttons for tools with unsupported requirements metadata', () => { + testExternalToolsRepository.getExternalTools = cy.stub().resolves([ + { + id: 77, + displayName: 'Preview Tool With Requirements', + description: 'Description for Preview Tool With Requirements', + scope: ToolScope.File, + types: [ToolType.Preview], + contentType: 'text/plain', + requirements: { + auxFilesExist: [{ formatTag: 'DP', formatVersion: '1.0' }] + } + } + ]) + + cy.customMount( + + + + ) + + cy.findByRole('link', { name: `Preview ${testFilePreview.name}` }).should('not.exist') }) }) diff --git a/tests/component/sections/edit-dataset-terms/EditGuestbook.spec.tsx b/tests/component/sections/edit-dataset-terms/EditGuestbook.spec.tsx index 3dff96744..b907f4fbc 100644 --- a/tests/component/sections/edit-dataset-terms/EditGuestbook.spec.tsx +++ b/tests/component/sections/edit-dataset-terms/EditGuestbook.spec.tsx @@ -331,7 +331,7 @@ describe('EditGuestbook', () => { ) }) - it('navigates with numeric version query param after successful submit for non-draft datasets', () => { + it('navigates with DRAFT version query param after successful submit for non-draft datasets', () => { ;(guestbookRepository.assignDatasetGuestbook as Cypress.Agent).resolves( undefined ) @@ -356,7 +356,7 @@ describe('EditGuestbook', () => { cy.findByRole('button', { name: 'Save Changes' }).click() cy.findByTestId('location-display').should( 'have.text', - '/datasets?persistentId=doi%3A10.5072%2FFK2%2FRELEASEDPID&version=1.0' + '/datasets?persistentId=doi%3A10.5072%2FFK2%2FRELEASEDPID&version=DRAFT' ) }) diff --git a/tests/component/sections/edit-dataset-terms/EditLicenseAndTerms.spec.tsx b/tests/component/sections/edit-dataset-terms/EditLicenseAndTerms.spec.tsx index 3c776f751..86ec0748e 100644 --- a/tests/component/sections/edit-dataset-terms/EditLicenseAndTerms.spec.tsx +++ b/tests/component/sections/edit-dataset-terms/EditLicenseAndTerms.spec.tsx @@ -357,11 +357,18 @@ describe('EditLicenseAndTerms', () => { cy.customMount( withProviders( - , - mockDatasetWithLicense + <> + + + , + DatasetMother.create({ + ...mockDatasetWithLicense, + persistentId: 'doi:10.5072/FK2/LICENSEPID', + version: DatasetVersionMother.createReleased() + }) ) ) @@ -371,6 +378,10 @@ describe('EditLicenseAndTerms', () => { cy.findByRole('button', { name: 'Save Changes' }).click() cy.findByText('The license for this dataset has been updated.').should('exist') + cy.findByTestId('location-display').should( + 'have.text', + '/datasets?persistentId=doi%3A10.5072%2FFK2%2FLICENSEPID&version=DRAFT' + ) }) it('displays success toast when custom terms are updated successfully', () => { diff --git a/tests/component/sections/edit-dataset-terms/EditTermsOfAccess.spec.tsx b/tests/component/sections/edit-dataset-terms/EditTermsOfAccess.spec.tsx index eb1a023e8..7e42b5f94 100644 --- a/tests/component/sections/edit-dataset-terms/EditTermsOfAccess.spec.tsx +++ b/tests/component/sections/edit-dataset-terms/EditTermsOfAccess.spec.tsx @@ -274,9 +274,14 @@ describe('EditTermsOfAccess', () => { cy.customMount( withProviders( - , + <> + + + , DatasetMother.create({ id: 123, + persistentId: 'doi:10.5072/FK2/TERMSPID', + version: DatasetVersionMother.createReleased(), termsOfUse: { termsOfAccess } }) ) @@ -288,6 +293,10 @@ describe('EditTermsOfAccess', () => { cy.findByRole('button', { name: 'Save Changes' }).click() cy.findByText('The terms for this dataset have been updated.').should('exist') + cy.findByTestId('location-display').should( + 'have.text', + '/datasets?persistentId=doi%3A10.5072%2FFK2%2FTERMSPID&version=DRAFT' + ) }) it('displays success toast when request access checkbox is toggled', () => { diff --git a/tests/component/sections/file/file-action-buttons/access-file-menu/FileToolOptions.spec.tsx b/tests/component/sections/file/file-action-buttons/access-file-menu/FileToolOptions.spec.tsx index 3efcb91de..d51121454 100644 --- a/tests/component/sections/file/file-action-buttons/access-file-menu/FileToolOptions.spec.tsx +++ b/tests/component/sections/file/file-action-buttons/access-file-menu/FileToolOptions.spec.tsx @@ -7,6 +7,7 @@ import { import { ExternalToolsProvider } from '@/shared/contexts/external-tools/ExternalToolsProvider' import { ExternalToolsMother } from '@tests/component/externalTools/domain/models/ExternalToolsMother' import { FileExternalToolResolvedMother } from '@tests/component/externalTools/domain/models/FileExternalToolResolvedMother' +import { ToolScope, ToolType } from '@/externalTools/domain/models/ExternalTool' const testExternalToolsRepository: ExternalToolsRepository = {} as ExternalToolsRepository const testFileExploreTool = ExternalToolsMother.createFileExploreTool() @@ -22,6 +23,17 @@ describe('FileToolOptions', () => { describe('FileExploreToolsOptions', () => { it('renders the tool options if file explore tools are available and compatible with the type', () => { + testExternalToolsRepository.getExternalTools = cy.stub().resolves([ + testFileExploreTool, + { + ...testFileExploreTool, + id: 33, + displayName: 'PDF Explore Tool', + contentType: 'application/pdf' + }, + testFileQueryTool + ]) + cy.customMount( @@ -32,6 +44,8 @@ describe('FileToolOptions', () => { cy.findByText('Configure Options').should('not.exist') cy.findByText('Explore Options').should('exist') cy.findByText('File Explore Tool').should('exist') + cy.findByText('PDF Explore Tool').should('not.exist') + cy.findByText('File Query Tool').should('not.exist') }) it('does not render the tool options if there are not applicable tools for the file type', () => { @@ -135,6 +149,71 @@ describe('FileToolOptions', () => { }) }) + it('calls getFileExternalToolResolved with preview=false and locale', () => { + testExternalToolsRepository.getFileExternalToolResolved = cy + .stub() + .as('getFileExternalToolResolved') + .resolves( + FileExternalToolResolvedMother.create({ + toolUrlResolved: 'https://example.com/query-tool' + }) + ) + + const fakeWindow = { + closed: false, + document: { title: '' }, + location: { href: '' }, + close: cy.stub().as('windowCloseStub') + } + + cy.window().then((win) => { + cy.stub(win, 'open').as('windowOpen').returns(fakeWindow) + }) + + cy.customMount( + + + + ) + + cy.findByText('File Query Tool').click() + + cy.get('@getFileExternalToolResolved') + .should('have.been.calledOnce') + .its('firstCall.args') + .then((args) => { + expect(args[0]).to.equal(123) + expect(args[1]).to.equal(testFileQueryTool.id) + expect(args[2]).to.have.property('preview', false) + expect(args[2]).to.have.property('locale').that.is.a('string').and.is.not.empty + }) + }) + + it('does not render file tools that require unsupported requirements metadata', () => { + testExternalToolsRepository.getExternalTools = cy.stub().resolves([ + { + id: 77, + displayName: 'File Explore Tool With Requirements', + description: 'Description for File Explore Tool With Requirements', + scope: ToolScope.File, + types: [ToolType.Explore], + contentType: 'text/plain', + requirements: { + auxFilesExist: [{ formatTag: 'DP', formatVersion: '1.0' }] + } + } + ]) + + cy.customMount( + + + + ) + + cy.findByText('Explore Options').should('not.exist') + cy.findByText('File Explore Tool With Requirements').should('not.exist') + }) + it('shows an error toast if fetching the tool URL fails', () => { testExternalToolsRepository.getFileExternalToolResolved = cy .stub() diff --git a/tests/component/sections/file/file-embedded-external-tool/FileEmbeddedExternalTool.spec.tsx b/tests/component/sections/file/file-embedded-external-tool/FileEmbeddedExternalTool.spec.tsx index 469c57039..689a59feb 100644 --- a/tests/component/sections/file/file-embedded-external-tool/FileEmbeddedExternalTool.spec.tsx +++ b/tests/component/sections/file/file-embedded-external-tool/FileEmbeddedExternalTool.spec.tsx @@ -5,8 +5,13 @@ import { WriteError } from '@iqss/dataverse-client-javascript' import { ExternalToolsMother } from '@tests/component/externalTools/domain/models/ExternalToolsMother' import { FileExternalToolResolvedMother } from '@tests/component/externalTools/domain/models/FileExternalToolResolvedMother' import { FileMother } from '@tests/component/files/domain/models/FileMother' +import { AccessRepository } from '@/access/domain/repositories/AccessRepository' +import { AccessRepositoryProvider } from '@/sections/access/AccessRepositoryProvider' +import { CustomTermsMother } from '@tests/component/dataset/domain/models/TermsOfUseMother' +import { FilePermissionsMother } from '@tests/component/files/domain/models/FilePermissionsMother' const externalToolsRepository: ExternalToolsRepository = {} as ExternalToolsRepository // Used for fetching the tool resolved URL +const accessRepository: AccessRepository = {} as AccessRepository const testFile = FileMother.createRealistic() // text/plain file const filePreviewTool = ExternalToolsMother.createFilePreviewTool() // id: 2 @@ -130,6 +135,56 @@ describe('FileEmbeddedExternalTool', () => { cy.findByTestId('external-tool-iframe').should('not.exist') }) + it('requires dataset terms acceptance before loading the external tool', () => { + const fileWithCustomTerms = FileMother.createRealistic({ + permissions: FilePermissionsMother.create({ + canDownloadFile: true, + canEditOwnerDataset: false, + canManageFilePermissions: false + }), + datasetCustomTerms: CustomTermsMother.create({ + termsOfUse: 'Preview requires accepting these custom terms.' + }) + }) + externalToolsRepository.getFileExternalToolResolved = cy + .stub() + .as('getFileExternalToolResolved') + .resolves(filePreviewToolResolved) + accessRepository.submitGuestbookForDatasetDownload = cy.stub().resolves('signed-url-dataset') + accessRepository.submitGuestbookForDatafileDownload = cy + .stub() + .as('submitGuestbookForDatafileDownload') + .resolves('signed-url-datafile') + accessRepository.submitGuestbookForDatafilesDownload = cy + .stub() + .resolves('signed-url-datafiles') + + cy.customMount( + + + + ) + + cy.findByRole('dialog').should('exist') + cy.findByText('Preview requires accepting these custom terms.').should('exist') + cy.findByTestId('external-tool-iframe').should('not.exist') + cy.get('@getFileExternalToolResolved').should('not.have.been.called') + + cy.findByRole('button', { name: 'Accept' }).click() + + cy.get('@submitGuestbookForDatafileDownload').should('have.been.calledOnce') + cy.findByRole('dialog').should('not.exist') + cy.findByTestId('external-tool-iframe') + .should('exist') + .should('have.attr', 'src', filePreviewToolResolved.toolUrlResolved) + }) + describe('error handling', () => { it('shows js dataverse error message if fetching the tool URL fails with a JSDataverseError', () => { externalToolsRepository.getFileExternalToolResolved = cy diff --git a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx index 1bba11036..8d767c06e 100644 --- a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx +++ b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx @@ -817,7 +817,8 @@ describe('Dataset', () => { it('downloads the dataset directly for dataset editors even when a guestbook is assigned', () => { const guestbookName = `Guestbook ${faker.datatype.uuid()}` - cy.wrap(DatasetHelper.createWithFiles(FileHelper.createMany(2))).then((dataset) => { + cy.wrap(DatasetHelper.createWithFiles(FileHelper.createMany(2))).then(async (dataset) => { + await TestsUtils.waitForNoLocks(dataset.persistentId) cy.wrap( GuestbookHelper.createAndGetByName(guestbookName).then(async (guestbook) => { await GuestbookHelper.assignToDataset(Number(dataset.id), guestbook.id) @@ -848,7 +849,8 @@ describe('Dataset', () => { 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) => { + cy.wrap(DatasetHelper.createWithFiles(FileHelper.createMany(2))).then(async (dataset) => { + await TestsUtils.waitForNoLocks(dataset.persistentId) cy.wrap( GuestbookHelper.createAndGetByName(guestbookName).then(async (guestbook) => { await GuestbookHelper.assignToDataset(Number(dataset.id), guestbook.id) @@ -878,6 +880,7 @@ 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 TestsUtils.waitForNoLocks(dataset.persistentId) await DatasetHelper.setCustomTermsOfUse(dataset.id, { termsOfUse: 'These are custom terms of use for testing' }) @@ -906,6 +909,7 @@ describe('Dataset', () => { 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 TestsUtils.waitForNoLocks(dataset.persistentId) await DatasetHelper.setCustomTermsOfUse(dataset.id, { termsOfUse: 'These are custom terms of use for testing' })