Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/file upload page boilerplate #375

Merged
merged 8 commits into from
Apr 23, 2024
Merged
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 packages/design-system/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
- **FormSelect:** ability to forward react ref to input.
- **FormTextArea:** ability to forward react ref to input.
- **FormFeedback:** remove `span: 9` from styles.
- **DropdownButtonItem:** extend Props Interface to accept `as` prop.

# [1.1.0](https://github.com/IQSS/dataverse-frontend/compare/@iqss/dataverse-design-system@1.0.1...@iqss/dataverse-design-system@1.1.0) (2024-03-12)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Dropdown as DropdownBS } from 'react-bootstrap'
import React, { ReactNode } from 'react'
import React, { ElementType, ReactNode } from 'react'

interface DropdownItemProps extends React.HTMLAttributes<HTMLElement> {
href?: string
eventKey?: string
disabled?: boolean
download?: string
children: ReactNode
as?: ElementType
}

export function DropdownButtonItem({
Expand All @@ -15,6 +16,7 @@ export function DropdownButtonItem({
disabled,
download,
children,
as,
...props
}: DropdownItemProps) {
return (
Expand All @@ -23,6 +25,7 @@ export function DropdownButtonItem({
eventKey={eventKey}
disabled={disabled}
download={download}
as={as}
{...props}>
{children}
</DropdownBS.Item>
Expand Down
5 changes: 5 additions & 0 deletions src/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { PageNotFound } from './sections/page-not-found/PageNotFound'
import { CreateDatasetFactory } from './sections/create-dataset/CreateDatasetFactory'
import { FileFactory } from './sections/file/FileFactory'
import { CollectionFactory } from './sections/collection/CollectionFactory'
import { UploadDatasetFilesFactory } from './sections/upload-dataset-files/UploadDatasetFilesFactory'
import { DatasetNonNumericVersion } from './dataset/domain/models/Dataset'

const router = createBrowserRouter(
Expand All @@ -31,6 +32,10 @@ const router = createBrowserRouter(
path: Route.CREATE_DATASET,
element: CreateDatasetFactory.create()
},
{
path: Route.UPLOAD_DATASET_FILES,
element: UploadDatasetFilesFactory.create()
},
{
path: Route.FILES,
element: FileFactory.create()
Expand Down
1 change: 1 addition & 0 deletions src/sections/Route.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export enum Route {
LOG_OUT = '/',
DATASETS = '/datasets',
CREATE_DATASET = '/datasets/create',
UPLOAD_DATASET_FILES = '/datasets/upload-files',
FILES = '/files',
COLLECTIONS = '/collections'
}
Original file line number Diff line number Diff line change
@@ -1,45 +1,76 @@
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router-dom'
import { Dataset } from '../../../../dataset/domain/models/Dataset'
import { DropdownButton, DropdownButtonItem } from '@iqss/dataverse-design-system'
import { EditDatasetPermissionsMenu } from './EditDatasetPermissionsMenu'
import { DeleteDatasetButton } from './DeleteDatasetButton'
import { DeaccessionDatasetButton } from './DeaccessionDatasetButton'
import { useTranslation } from 'react-i18next'
import { useNotImplementedModal } from '../../../not-implemented/NotImplementedModalContext'
import { useSession } from '../../../session/SessionContext'
import { Route } from '../../../Route.enum'

interface EditDatasetMenuProps {
dataset: Dataset
}

enum EditDatasetMenuItems {
FILES_UPLOAD = 'filesUpload',
METADATA = 'metadata',
TERMS = 'terms',
PERMISSIONS = 'permissions',
PRIVATE_URL = 'privateUrl',
THUMBNAILS_PLUS_WIDGETS = 'thumbnailsPlusWidgets'
}

export function EditDatasetMenu({ dataset }: EditDatasetMenuProps) {
const { user } = useSession()
const { showModal } = useNotImplementedModal()
const { t } = useTranslation('dataset')
const navigate = useNavigate()

const handleOnSelect = (eventKey: EditDatasetMenuItems | string | null) => {
if (eventKey === EditDatasetMenuItems.FILES_UPLOAD) {
navigate(`${Route.UPLOAD_DATASET_FILES}?persistentId=${dataset.persistentId}`)
return
}
showModal()
}

if (!user || !dataset.permissions.canUpdateDataset) {
return <></>
}
const { showModal } = useNotImplementedModal()
const { t } = useTranslation('dataset')

return (
<DropdownButton
onSelect={showModal}
onSelect={handleOnSelect}
id={`edit-dataset-menu`}
title={t('datasetActionButtons.editDataset.title')}
asButtonGroup
variant="secondary"
disabled={dataset.checkIsLockedFromEdits(user.persistentId)}>
<DropdownButtonItem disabled={!dataset.hasValidTermsOfAccess}>
<DropdownButtonItem
eventKey={EditDatasetMenuItems.FILES_UPLOAD}
as="button"
disabled={!dataset.hasValidTermsOfAccess}>
{t('datasetActionButtons.editDataset.filesUpload')}
</DropdownButtonItem>
<DropdownButtonItem disabled={!dataset.hasValidTermsOfAccess}>
<DropdownButtonItem
eventKey={EditDatasetMenuItems.METADATA}
as="button"
disabled={!dataset.hasValidTermsOfAccess}>
{t('datasetActionButtons.editDataset.metadata')}
</DropdownButtonItem>
<DropdownButtonItem>{t('datasetActionButtons.editDataset.terms')}</DropdownButtonItem>
<DropdownButtonItem eventKey={EditDatasetMenuItems.TERMS} as="button">
{t('datasetActionButtons.editDataset.terms')}
</DropdownButtonItem>
<EditDatasetPermissionsMenu dataset={dataset} />
{(dataset.permissions.canManageDatasetPermissions ||
dataset.permissions.canManageFilesPermissions) && (
<DropdownButtonItem>{t('datasetActionButtons.editDataset.privateUrl')}</DropdownButtonItem>
<DropdownButtonItem eventKey={EditDatasetMenuItems.PRIVATE_URL} as="button">
{t('datasetActionButtons.editDataset.privateUrl')}
</DropdownButtonItem>
)}
<DropdownButtonItem>
<DropdownButtonItem eventKey={EditDatasetMenuItems.THUMBNAILS_PLUS_WIDGETS} as="button">
{t('datasetActionButtons.editDataset.thumbnailsPlusWidgets')}
</DropdownButtonItem>
<DeleteDatasetButton dataset={dataset} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
import { Button } from '@iqss/dataverse-design-system'
import { useNavigate } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { PlusLg } from 'react-bootstrap-icons'
import { Button } from '@iqss/dataverse-design-system'
import { useSession } from '../../../session/SessionContext'
import styles from './DatasetUploadFilesButton.module.scss'
import { useTranslation } from 'react-i18next'
import { useDataset } from '../../DatasetContext'
import { useNotImplementedModal } from '../../../not-implemented/NotImplementedModalContext'
import { Route } from '../../../Route.enum'
import styles from './DatasetUploadFilesButton.module.scss'

export function DatasetUploadFilesButton() {
const { t } = useTranslation('dataset')
const { user } = useSession()
const { dataset } = useDataset()
const handleClick = () => {
// TODO - Implement upload files
showModal()
}
const { showModal } = useNotImplementedModal()
const navigate = useNavigate()

if (!user || !dataset?.permissions.canUpdateDataset) {
return <></>
}

const handleClick = () => {
navigate(`${Route.UPLOAD_DATASET_FILES}?persistentId=${dataset.persistentId}`)
}

return (
<Button
type="button"
Expand Down
40 changes: 40 additions & 0 deletions src/sections/upload-dataset-files/UploadDatasetFiles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useEffect } from 'react'
import { FileRepository } from '../../files/domain/repositories/FileRepository'
import { useLoading } from '../loading/LoadingContext'
import { useDataset } from '../dataset/DatasetContext'
import { PageNotFound } from '../page-not-found/PageNotFound'
import { BreadcrumbsGenerator } from '../shared/hierarchy/BreadcrumbsGenerator'

interface UploadDatasetFilesProps {
fileRepository: FileRepository
}

export const UploadDatasetFiles = ({
fileRepository: _fileRepository
}: UploadDatasetFilesProps) => {
const { setIsLoading } = useLoading()
const { dataset, isLoading } = useDataset()

useEffect(() => {
setIsLoading(isLoading)
}, [isLoading])

if (isLoading) {
return <p>Temporary Loading until having shape of skeleton</p>
}

return (
<>
{!dataset ? (
<PageNotFound />
) : (
<>
<BreadcrumbsGenerator hierarchy={dataset.hierarchy} />
<article>
<p>Metadata Files uploading goes here</p>
</article>
</>
)}
</>
)
}
26 changes: 26 additions & 0 deletions src/sections/upload-dataset-files/UploadDatasetFilesFactory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ReactElement } from 'react'
import { useSearchParams } from 'react-router-dom'
import { DatasetJSDataverseRepository } from '../../dataset/infrastructure/repositories/DatasetJSDataverseRepository'
import { FileJSDataverseRepository } from '../../files/infrastructure/FileJSDataverseRepository'
import { DatasetProvider } from '../dataset/DatasetProvider'
import { UploadDatasetFiles } from './UploadDatasetFiles'

const datasetRepository = new DatasetJSDataverseRepository()
const fileRepository = new FileJSDataverseRepository()

export class UploadDatasetFilesFactory {
static create(): ReactElement {
return <UploadDatasetFilesWithSearchParams />
}
}

function UploadDatasetFilesWithSearchParams() {
const [searchParams] = useSearchParams()
const persistentId = searchParams.get('persistentId') ?? undefined

return (
<DatasetProvider repository={datasetRepository} searchParams={{ persistentId: persistentId }}>
<UploadDatasetFiles fileRepository={fileRepository} />
</DatasetProvider>
)
}
24 changes: 24 additions & 0 deletions src/stories/upload-dataset-files/UploadDatasetFiles.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Meta, StoryObj } from '@storybook/react'
import { WithI18next } from '../WithI18next'
import { WithLayout } from '../WithLayout'
import { UploadDatasetFiles } from '../../sections/upload-dataset-files/UploadDatasetFiles'
import { FileMockRepository } from '../file/FileMockRepository'
import { WithDataset } from '../dataset/WithDataset'

const meta: Meta<typeof UploadDatasetFiles> = {
title: 'Pages/Upload Dataset Files',
component: UploadDatasetFiles,
decorators: [WithI18next],
parameters: {
// Sets the delay for all stories.
chromatic: { delay: 15000, pauseAnimationAtEnd: true }
}
}

export default meta
type Story = StoryObj<typeof UploadDatasetFiles>

export const Default: Story = {
decorators: [WithLayout, WithDataset],
render: () => <UploadDatasetFiles fileRepository={new FileMockRepository()} />
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { DatasetRepository } from '../../../../src/dataset/domain/repositories/DatasetRepository'
import { DatasetMother } from '../../dataset/domain/models/DatasetMother'
import { FileRepository } from '../../../../src/files/domain/repositories/FileRepository'
import { Dataset as DatasetModel } from '../../../../src/dataset/domain/models/Dataset'
import { ReactNode } from 'react'
import { DatasetProvider } from '../../../../src/sections/dataset/DatasetProvider'
import { UploadDatasetFiles } from '../../../../src/sections/upload-dataset-files/UploadDatasetFiles'

const fileRepository: FileRepository = {} as FileRepository
const datasetRepository: DatasetRepository = {} as DatasetRepository

describe('Dataset', () => {
const mountWithDataset = (component: ReactNode, dataset: DatasetModel | undefined) => {
const searchParams = { persistentId: 'some-persistent-id' }
datasetRepository.getByPersistentId = cy.stub().resolves(dataset)

cy.customMount(
<DatasetProvider repository={datasetRepository} searchParams={searchParams}>
{component}
</DatasetProvider>
)
}

it('renders skeleton while loading', () => {
const testDataset = DatasetMother.create()

mountWithDataset(<UploadDatasetFiles fileRepository={fileRepository} />, testDataset)

cy.findByText('Temporary Loading until having shape of skeleton').should('exist')
cy.findByText(testDataset.version.title).should('not.exist')
})

it('renders page not found when dataset is null', () => {
const emptyDataset = DatasetMother.createEmpty()

mountWithDataset(<UploadDatasetFiles fileRepository={fileRepository} />, emptyDataset)

cy.findByText('Page Not Found').should('exist')
})

it('renders the breadcrumbs', () => {
const testDataset = DatasetMother.create()

mountWithDataset(<UploadDatasetFiles fileRepository={fileRepository} />, testDataset)

cy.findByText('Dataset Title').should('exist').should('have.class', 'active')
cy.findByRole('link', { name: 'Root' }).should('exist')
})
})
Loading