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

337 multiple values field type #351

Merged
merged 23 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b70f1ee
refactor: move Author FormGroup to separate component
ekraffmiller Mar 19, 2024
7a9730e
fix: add validationErrors prop
ekraffmiller Mar 19, 2024
1880562
feat: add index to formGroup
ekraffmiller Mar 19, 2024
9802738
feat: add DynamicFieldsButtons.tsx
ekraffmiller Mar 19, 2024
c4c8b5f
add logic for add/remove row, add Storybook story
ekraffmiller Mar 21, 2024
093743a
fix: remove log statement
ekraffmiller Mar 21, 2024
ea8c979
feat: add useMultipleFields.tsx to manage state of AuthorFormGroup.tsx
ekraffmiller Mar 21, 2024
846d88e
fix: handleFieldChange
ekraffmiller Mar 21, 2024
9371163
fix: use updateFormData in AuthorFormGroup.tsx
ekraffmiller Mar 22, 2024
d03c2e6
fix: e2e test
ekraffmiller Mar 22, 2024
f749228
fix: add required attribute to FormInput.tsx
ekraffmiller Mar 22, 2024
2f1f6e4
fix: move AuthorFormGroup.stories.tsx to Sections folder
ekraffmiller Mar 22, 2024
f81af84
fix: alignment of the Add Button
ekraffmiller Apr 3, 2024
cd7fb93
fix: add translation text for DynamicFieldsButtons.tsx
ekraffmiller Apr 3, 2024
6959450
fix: alignment of Author Inputs, add constants for better code readab…
ekraffmiller Apr 3, 2024
1648ef8
feat: AuthorFormGroup component test (initial version)
ekraffmiller Apr 3, 2024
0bd697a
fix: AuthFormGroup.tsx add Name label
ekraffmiller Apr 3, 2024
8ca7e9a
fix: alignment of DynamicFieldsButtons
ekraffmiller Apr 3, 2024
41bb085
feat: update unit tests for AuthorFormGroup.tsx and CreateDatasetForm…
ekraffmiller Apr 3, 2024
1bcd0bb
fix: find Author Name CreateDatasetForm.spec.tsx
ekraffmiller Apr 3, 2024
aa5d959
fix: use the same validation logic on AuthorFormGroup.tsx as is used …
ekraffmiller Apr 11, 2024
5b5cf85
fix: change button type of DynamicFieldsButtons.tsx to prevent form s…
ekraffmiller Apr 11, 2024
8191241
resolve merge conflicts
ekraffmiller Apr 15, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ interface FormInputProps extends React.HTMLAttributes<FormInputElement> {
isValid?: boolean
isInvalid?: boolean
disabled?: boolean
value?: string | number
required?: boolean
}

export function FormInput({
Expand All @@ -22,6 +24,8 @@ export function FormInput({
isInvalid,
disabled,
withinMultipleFieldsGroup,
value,
required,
...props
}: FormInputProps) {
return (
Expand All @@ -37,6 +41,8 @@ export function FormInput({
isValid={isValid}
isInvalid={isInvalid}
disabled={disabled}
value={value}
required={required}
{...props}
/>
</FormElementLayout>
Expand Down
4 changes: 3 additions & 1 deletion public/locales/en/createDataset.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@
"submitting": "Submitting...",
"success": "Form submitted successfully!",
"failed": "Error: Submission failed."
}
},
"addRowButton": "Add",
"deleteRowButton": "Delete"
},
"datasetFormStates": {
"submitting": "Form Submitting",
Expand Down
24 changes: 17 additions & 7 deletions src/dataset/domain/useCases/validateDataset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,23 @@ export function validateDataset(dataset: DatasetDTO) {
isValid = false
}

if (
isArrayOfSubfieldValue(dataset.metadataBlocks[0].fields.author) &&
!dataset.metadataBlocks[0].fields.author[0].authorName
) {
if (isArrayOfSubfieldValue(errors.metadataBlocks[0].fields.author)) {
errors.metadataBlocks[0].fields.author[0].authorName = AUTHOR_NAME_REQUIRED
isValid = false
if (isArrayOfSubfieldValue(dataset.metadataBlocks[0].fields.author)) {
for (let i = 0; i < dataset.metadataBlocks[0].fields.author.length; i++) {
if (!dataset.metadataBlocks[0].fields.author[i].authorName) {
if (isArrayOfSubfieldValue(errors.metadataBlocks[0].fields.author)) {
// Check if the errors array has enough elements
if (i < errors.metadataBlocks[0].fields.author.length) {
errors.metadataBlocks[0].fields.author[i].authorName = AUTHOR_NAME_REQUIRED
console.log('invalid author name')
isValid = false
} else {
// If the errors array does not have enough elements, add a new one
errors.metadataBlocks[0].fields.author.push({ authorName: AUTHOR_NAME_REQUIRED })
console.log('invalid author name')
isValid = false
}
}
}
}
}

Expand Down
96 changes: 96 additions & 0 deletions src/sections/create-dataset/AuthorFormGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { Form } from '@iqss/dataverse-design-system'
import { useTranslation } from 'react-i18next'
import { SubmissionStatus } from './useCreateDatasetForm'
import { DatasetMetadataSubField } from '../../dataset/domain/models/Dataset'
import { Col, Row } from '@iqss/dataverse-design-system'
import { DynamicFieldsButtons } from './dynamic-fields-buttons/DynamicFieldsButtons'

import { ChangeEvent } from 'react'
import _ from 'lodash'
import { useMultipleFields } from './useMultipleFields'
import { DatasetDTO } from '../../dataset/domain/useCases/DTOs/DatasetDTO'
interface AuthorFormGroupProps {
submissionStatus: SubmissionStatus
initialAuthorFields: DatasetMetadataSubField[]
updateFormData: (name: string, value: string | DatasetMetadataSubField[]) => void
validationErrors: DatasetDTO
}

export function AuthorFormGroup({
MellyGray marked this conversation as resolved.
Show resolved Hide resolved
submissionStatus,
initialAuthorFields,
updateFormData,
validationErrors
}: AuthorFormGroupProps) {
const { t } = useTranslation('createDataset')
const { multipleFields, setMultipleFields, addField, removeField } =
useMultipleFields(initialAuthorFields)

const isAuthorInvalid = (index: number) => {
const subfieldArray = validationErrors.metadataBlocks[0].fields
.author as DatasetMetadataSubField[]
const retValue = subfieldArray[index] ? !!subfieldArray[index].authorName : false
return retValue
}
const handleFieldChange = (index: number, event: ChangeEvent<HTMLInputElement>) => {
const updatedAuthorFields = _.cloneDeep(multipleFields)
updatedAuthorFields[index].authorName = event.target.value
setMultipleFields(updatedAuthorFields)
updateFormData('metadataBlocks.0.fields.author', updatedAuthorFields)
}
const FIRST_AUTHOR = 0
const initialAuthorFieldState = { authorName: '' }
return (
<>
{multipleFields.map((author, index) => (
<Form.Group controlId="author-name" required key={index}>
<Row>
<Col sm={3}>
{index === FIRST_AUTHOR && (
<Form.Group required controlId={'author-title'} as={Col}>
<Form.Group.Label message={t('datasetForm.fields.authorName.tooltip')}>
{t('datasetForm.fields.authorName.label')}
</Form.Group.Label>
</Form.Group>
)}
</Col>
<Col sm={6}>
<Form.Group.Label required message={t('datasetForm.fields.authorName.tooltip')}>
Name
</Form.Group.Label>
<Form.Group controlId={'author-name'} as={Col} required>
<Form.Group.Input
disabled={submissionStatus === SubmissionStatus.IsSubmitting}
type="text"
name={`metadataBlocks.0.fields.author.${index}.authorName`}
onChange={(event: ChangeEvent<HTMLInputElement>) =>
handleFieldChange(index, event)
}
isInvalid={isAuthorInvalid(index)}
value={author.authorName}
required
/>
<Form.Group.Feedback type="invalid">
{t('datasetForm.fields.authorName.feedback')}
</Form.Group.Feedback>
</Form.Group>
</Col>
<Col sm={3}>
<Form.Group controlId={'author-button'} as={Col} required>
<DynamicFieldsButtons
originalField={index === FIRST_AUTHOR}
onAddButtonClick={() => {
addField(index, initialAuthorFieldState)
}}
onRemoveButtonClick={() => {
removeField(index)
}}
/>
</Form.Group>
</Col>
</Row>
</Form.Group>
))}
</>
)
}
26 changes: 8 additions & 18 deletions src/sections/create-dataset/CreateDatasetForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Route } from '../Route.enum'
import { useNavigate } from 'react-router-dom'
import { useDatasetValidator } from './useDatasetValidator'
import { DatasetMetadataSubField } from '../../dataset/domain/models/Dataset'
import { AuthorFormGroup } from './AuthorFormGroup'

interface CreateDatasetFormProps {
repository: DatasetRepository
Expand Down Expand Up @@ -81,24 +82,13 @@ export function CreateDatasetForm({ repository }: CreateDatasetFormProps) {
{t('datasetForm.fields.title.feedback')}
</Form.Group.Feedback>
</Form.Group>
<Form.Group controlId="author-name" required>
<Form.Group.Label message={t('datasetForm.fields.authorName.tooltip')}>
{t('datasetForm.fields.authorName.label')}
</Form.Group.Label>
<Form.Group.Input
disabled={submissionStatus === SubmissionStatus.IsSubmitting}
type="text"
name="metadataBlocks.0.fields.author.0.authorName"
onChange={handleFieldChange}
isInvalid={
!!(validationErrors.metadataBlocks[0].fields.author as DatasetMetadataSubField[])[0]
.authorName
}
/>
<Form.Group.Feedback type="invalid">
{t('datasetForm.fields.authorName.feedback')}
</Form.Group.Feedback>
</Form.Group>
<AuthorFormGroup
submissionStatus={submissionStatus}
updateFormData={updateFormData}
initialAuthorFields={
formData.metadataBlocks[0].fields['author'] as DatasetMetadataSubField[]
}
validationErrors={validationErrors}></AuthorFormGroup>
<Form.Group controlId="contact-email" required>
<Form.Group.Label message={t('datasetForm.fields.datasetContactEmail.tooltip')}>
{t('datasetForm.fields.datasetContactEmail.label')}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.container {
display: flex;
margin: 2.5em;
}

.icon {
display: inline-block;
vertical-align: -0.125em;
}

.overlay-container {
width: fit-content;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Button } from '@iqss/dataverse-design-system'
import styles from './DynamicFieldsButtons.module.scss'
import { MouseEvent } from 'react'
import { Dash, Plus } from 'react-bootstrap-icons'
import { Tooltip } from '@iqss/dataverse-design-system'
import { useTranslation } from 'react-i18next'

interface AddFieldButtonsProps {
originalField?: boolean
onAddButtonClick: (event: MouseEvent<HTMLButtonElement>) => void
onRemoveButtonClick: (event: MouseEvent<HTMLButtonElement>) => void
}

export function DynamicFieldsButtons({
originalField,
onAddButtonClick,
onRemoveButtonClick
}: AddFieldButtonsProps) {
const { t } = useTranslation('createDataset')
return (
<div className={styles.container}>
<Tooltip placement="top" overlay={t('datasetForm.addRowButton')}>
<div className={styles['overlay-container']}>
<Button variant="secondary" onClick={onAddButtonClick}>
<Plus className={styles.icon} title="Add" />
</Button>
</div>
</Tooltip>
{!originalField && (
<Tooltip placement="top" overlay={t('datasetForm.deleteRowButton')}>
<div className={styles['overlay-container']}>
<Button variant="secondary" withSpacing onClick={onRemoveButtonClick}>
<Dash className={styles.icon} title="Delete" />
</Button>
</div>
</Tooltip>
)}
</div>
)
}
8 changes: 4 additions & 4 deletions src/sections/create-dataset/useDatasetFormData.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import { useState } from 'react'
import { DatasetDTO, initialDatasetDTO } from '../../dataset/domain/useCases/DTOs/DatasetDTO'
import _ from 'lodash'
import { DatasetMetadataSubField } from '../../dataset/domain/models/Dataset'

const initialState: DatasetDTO = JSON.parse(JSON.stringify(initialDatasetDTO)) as DatasetDTO
export const useDatasetFormData = (
datasetIsValid: (formData: DatasetDTO) => boolean
): {
formData: DatasetDTO
updateFormData: (name: string, value: string) => void
updateFormData: (name: string, value: string | DatasetMetadataSubField[]) => void
} => {
const [formData, setFormData] = useState(initialState)

const updateFormData = (name: string, value: string) => {
const updateFormData = (name: string, value: string | DatasetMetadataSubField[]) => {
const updatedFormData = _.cloneDeep(getUpdatedFormData(formData, name, value))

setFormData(updatedFormData)
datasetIsValid(updatedFormData)
}

const getUpdatedFormData = (
currentFormData: DatasetDTO,
name: string,
value: string
value: string | DatasetMetadataSubField[]
): DatasetDTO => {
const objectFromPath: DatasetDTO = initialState

Expand Down
21 changes: 21 additions & 0 deletions src/sections/create-dataset/useMultipleFields.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useState } from 'react'
import _ from 'lodash'
import { DatasetMetadataSubField } from '../../dataset/domain/models/Dataset'

export function useMultipleFields(initialFields: DatasetMetadataSubField[]) {
const [multipleFields, setMultipleFields] = useState(initialFields)

const addField = (index: number, newField: DatasetMetadataSubField) => {
const updatedFields = _.cloneDeep(multipleFields)
updatedFields.splice(index + 1, 0, newField)
setMultipleFields(updatedFields)
}

const removeField = (index: number) => {
const updatedFields = _.cloneDeep(multipleFields)
updatedFields.splice(index, 1)
setMultipleFields(updatedFields)
}

return { multipleFields, setMultipleFields, addField, removeField }
}
29 changes: 29 additions & 0 deletions src/stories/create-dataset/AuthorFormGroup.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { StoryObj, Meta } from '@storybook/react'
import { WithLayout } from '../WithLayout'
import { WithI18next } from '../WithI18next'
import { AuthorFormGroup } from '../../sections/create-dataset/AuthorFormGroup'
import { SubmissionStatus } from '../../sections/create-dataset/useCreateDatasetForm'
import { initialState } from '../../sections/create-dataset/useDatasetValidator'
import { DatasetMetadataSubField } from '../../dataset/domain/models/Dataset'
import { initialDatasetDTO } from '../../../src/dataset/domain/useCases/DTOs/DatasetDTO'

const meta: Meta<typeof AuthorFormGroup> = {
title: 'Sections/Create Dataset Page/AuthorFormGroup',
component: AuthorFormGroup,
decorators: [WithI18next, WithLayout]
}
export default meta
type Story = StoryObj<typeof AuthorFormGroup>

export const Default: Story = {
MellyGray marked this conversation as resolved.
Show resolved Hide resolved
render: () => (
<AuthorFormGroup
submissionStatus={SubmissionStatus.NotSubmitted}
updateFormData={() => {}}
initialAuthorFields={
initialState.metadataBlocks[0].fields['author'] as DatasetMetadataSubField[]
}
validationErrors={initialDatasetDTO}
/>
)
}
38 changes: 38 additions & 0 deletions tests/component/create-dataset/AuthorFormGroup.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { AuthorFormGroup } from '../../../src/sections/create-dataset/AuthorFormGroup'
import { SubmissionStatus } from '../../../src/sections/create-dataset/useCreateDatasetForm'
import { initialDatasetDTO } from '../../../src/dataset/domain/useCases/DTOs/DatasetDTO'
describe('AuthorFormGroup', () => {
const initialAuthorFields = [{ authorName: '' }]
const datasetDTO = initialDatasetDTO
it('renders and allows adding and removing author fields', () => {
const updateFormDataMock = cy.stub()

cy.customMount(
<AuthorFormGroup
submissionStatus={SubmissionStatus.NotSubmitted}
initialAuthorFields={initialAuthorFields}
updateFormData={updateFormDataMock}
validationErrors={datasetDTO}
/>
)
cy.findByLabelText(/Name/i).should('exist')
// Simulate typing in the first author field
cy.findByLabelText(/Name/i).type('Test author name')

// Simulate clicking the add button to add a new author field
cy.contains('button', 'Add').should('exist')
cy.contains('button', 'Add').click()

// Assert that a new author field is added
const secondAuthorInput = 'input[name="metadataBlocks.0.fields.author.1.authorName"]'
// Check if the initial author field is rendered
cy.get(secondAuthorInput).should('exist')

// Simulate clicking the remove button on the first author field
cy.contains('button', 'Delete').should('exist')
cy.contains('button', 'Delete').click()

// Assert that the new author field is removed
cy.get(secondAuthorInput).should('not.exist')
})
})
Loading
Loading