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

Fix/393 Match api email validation #402

Merged
merged 4 commits into from
Jun 6, 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
29 changes: 28 additions & 1 deletion src/metadata-block-info/domain/models/fieldValidations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { DateFormats } from './MetadataBlockInfo'

export function isValidEmail(email: string): boolean {
const EMAIL_REGEX =
/[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/
/[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])/
return EMAIL_REGEX.test(email)
}

Expand Down Expand Up @@ -76,3 +76,30 @@ export function isValidDateFormat(dateString: string, acceptedFormat?: DateForma
)
}
}

/**
* Extracts the invalid value from the error message, removing 'Validation Failed:' and everything after '(Invalid value:'
* @param errorMessage
* @returns the invalid value or null if it can't be extracted
* @example
* getValidationFailedFieldError("Validation Failed: Point of Contact E-mail test@test.c is not a valid email address. (Invalid value:edu.harvard.iq.dataverse.DatasetFieldValueValue[ id=null ]).java.util.stream.ReferencePipeline$3@561b5200")
* // returns "Point of Contact E-mail test@test.c is not a valid email address."
*/

export function getValidationFailedFieldError(errorMessage: string): string | null {
const validationFailedKeyword = 'Validation Failed:'
const invalidValueKeyword = '(Invalid value:'

const validationFailedKeywordIndex = errorMessage.indexOf(validationFailedKeyword)
const invalidValueKeywordIndex = errorMessage.indexOf(invalidValueKeyword)

if (validationFailedKeywordIndex !== -1 && invalidValueKeywordIndex !== -1) {
const start = validationFailedKeywordIndex + validationFailedKeyword.length
const end = invalidValueKeywordIndex
const extractedValue = errorMessage.slice(start, end).trim()

return extractedValue
}

return null
}
2 changes: 1 addition & 1 deletion src/sections/create-dataset/CreateDataset.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function CreateDataset({
if (!isLoadingMetadataBlocksConfiguration) {
setIsLoading(false)
}
}, [isLoading, isLoadingMetadataBlocksConfiguration])
}, [isLoading, isLoadingMetadataBlocksConfiguration, setIsLoading])

return (
<>
Expand Down
3 changes: 3 additions & 0 deletions src/sections/create-dataset/DatasetForm.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.form-container {
scroll-margin-top: 62px;
}
31 changes: 21 additions & 10 deletions src/sections/create-dataset/DatasetForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { RequiredFieldText } from '../shared/form/RequiredFieldText/RequiredFiel
import { SeparationLine } from '../shared/layout/SeparationLine/SeparationLine'
import { MetadataBlockFormFields } from './MetadataBlockFormFields'
import { Route } from '../Route.enum'
import styles from './DatasetForm.module.scss'

interface DatasetFormProps {
repository: DatasetRepository
Expand All @@ -29,9 +30,15 @@ export const DatasetForm = ({
}: DatasetFormProps) => {
const navigate = useNavigate()
const { t } = useTranslation('createDataset')

const accordionRef = useRef<HTMLDivElement>(null)
const formContainerRef = useRef<HTMLDivElement>(null)

const { submissionStatus, submitForm } = useCreateDatasetForm(repository, collectionId)
const { submissionStatus, createError, submitForm } = useCreateDatasetForm(
repository,
collectionId,
onCreateDatasetError
)

const isErrorLoadingMetadataBlocks = Boolean(errorLoadingMetadataBlocks)

Expand All @@ -40,17 +47,11 @@ export const DatasetForm = ({
defaultValues: formDefaultValues
})

const formHasErrors = Object.keys(form.formState.errors).length > 0

const handleCancel = (event: MouseEvent<HTMLButtonElement>) => {
event.preventDefault()
navigate(Route.HOME)
}

const disableSubmitButton = useMemo(() => {
return isErrorLoadingMetadataBlocks || submissionStatus === SubmissionStatus.IsSubmitting
}, [isErrorLoadingMetadataBlocks, submissionStatus])

const onInvalidSubmit = (errors: FieldErrors<CreateDatasetFormValues>) => {
if (!accordionRef.current) return
/*
Expand Down Expand Up @@ -82,8 +83,18 @@ export const DatasetForm = ({
})
}

function onCreateDatasetError() {
if (formContainerRef.current) {
formContainerRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
}

const disableSubmitButton = useMemo(() => {
return isErrorLoadingMetadataBlocks || submissionStatus === SubmissionStatus.IsSubmitting
}, [isErrorLoadingMetadataBlocks, submissionStatus])

return (
<div>
<div className={styles['form-container']} ref={formContainerRef}>
<RequiredFieldText />
{isErrorLoadingMetadataBlocks && (
<Alert variant="danger" dismissible={false}>
Expand All @@ -97,9 +108,9 @@ export const DatasetForm = ({
{submissionStatus === SubmissionStatus.SubmitComplete && (
<p>{t('datasetForm.status.success')}</p>
)}
{(submissionStatus === SubmissionStatus.Errored || formHasErrors) && (
{submissionStatus === SubmissionStatus.Errored && (
<Alert variant={'danger'} customHeading={t('validationAlert.title')} dismissible={false}>
{t('validationAlert.content')}
{createError}
</Alert>
)}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMemo } from 'react'
import { useCallback, useMemo } from 'react'
import { Controller, useFieldArray, useFormContext } from 'react-hook-form'
import { Col, Form, Row } from '@iqss/dataverse-design-system'
import { TypeMetadataFieldOptions } from '../../../../../metadata-block-info/domain/models/MetadataBlockInfo'
Expand Down Expand Up @@ -36,21 +36,21 @@ export const PrimitiveMultiple = ({
control: control
})

const builtFieldNameWithIndex = (fieldIndex: number) => {
return MetadataFieldsHelper.defineFieldName(
name,
metadataBlockName,
compoundParentName,
fieldIndex
)
}

// We give the label the same ID as the first field, so that clicking on the label focuses the first field
const controlID = useMemo(
() => builtFieldNameWithIndex(0),
const builtFieldNameWithIndex = useCallback(
(fieldIndex: number) => {
return MetadataFieldsHelper.defineFieldName(
name,
metadataBlockName,
compoundParentName,
fieldIndex
)
},
[name, metadataBlockName, compoundParentName]
)

// We give the label the same ID as the first field, so that clicking on the label focuses the first field
const controlID = useMemo(() => builtFieldNameWithIndex(0), [builtFieldNameWithIndex])

const handleOnAddField = (index: number) => {
insert(
index + 1,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MetadataBlockInfo } from '../../../metadata-block-info/domain/models/MetadataBlockInfo'
import { type MetadataBlockInfo } from /* istanbul ignore next */ '../../../metadata-block-info/domain/models/MetadataBlockInfo'
import { MetadataFormField } from './MetadataFormField'

interface Props {
Expand Down
46 changes: 37 additions & 9 deletions src/sections/create-dataset/useCreateDatasetForm.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { createDataset } from '../../dataset/domain/useCases/createDataset'
import { DatasetRepository } from '../../dataset/domain/repositories/DatasetRepository'
import { type CreateDatasetFormValues, MetadataFieldsHelper } from './MetadataFieldsHelper'
import { getValidationFailedFieldError } from '../../metadata-block-info/domain/models/fieldValidations'
import { Route } from '../Route.enum'

export enum SubmissionStatus {
Expand All @@ -12,18 +14,35 @@ export enum SubmissionStatus {
Errored = 'Errored'
}

type UseCreateDatasetFormReturnType =
| {
submissionStatus:
| SubmissionStatus.NotSubmitted
| SubmissionStatus.IsSubmitting
| SubmissionStatus.SubmitComplete
submitForm: (formData: CreateDatasetFormValues) => void
createError: null
}
| {
submissionStatus: SubmissionStatus.Errored
submitForm: (formData: CreateDatasetFormValues) => void
createError: string
}

export function useCreateDatasetForm(
repository: DatasetRepository,
collectionId: string
): {
submissionStatus: SubmissionStatus
submitForm: (formData: CreateDatasetFormValues) => void
} {
collectionId: string,
onCreateErrorCallback: () => void
): UseCreateDatasetFormReturnType {
const [submissionStatus, setSubmissionStatus] = useState<SubmissionStatus>(
SubmissionStatus.NotSubmitted
)
const [createError, setCreateError] = useState<string | null>(null)

const navigate = useNavigate()

const { t } = useTranslation('createDataset')

const submitForm = (formData: CreateDatasetFormValues): void => {
setSubmissionStatus(SubmissionStatus.IsSubmitting)

Expand All @@ -35,20 +54,29 @@ export function useCreateDatasetForm(

createDataset(repository, formattedFormValues, collectionId)
.then(({ persistentId }) => {
setCreateError(null)
setSubmissionStatus(SubmissionStatus.SubmitComplete)
navigate(`${Route.DATASETS}?persistentId=${persistentId}`, {
state: { created: true }
})
return
})
.catch((e) => {
console.error(e)
.catch((err) => {
const errorMessage =
err instanceof Error && err.message
? getValidationFailedFieldError(err.message) ?? err.message
: t('validationAlert.content')

setCreateError(errorMessage)
setSubmissionStatus(SubmissionStatus.Errored)

onCreateErrorCallback()
})
}

return {
submissionStatus,
submitForm
}
submitForm,
createError
} as UseCreateDatasetFormReturnType
}
Original file line number Diff line number Diff line change
Expand Up @@ -4838,4 +4838,32 @@ export class MetadataBlockInfoMother {
}
]
}

static wrongCollectionMetadataBlocksInfo(): MetadataBlockInfo[] {
return [
{
id: 1,
name: 'citation',
displayName: 'Citation Metadata',
displayOnCreate: true,
metadataFields: {
title: {
name: 'title',
displayName: 'Title',
title: 'Title',
type: 'TEXT',
typeClass: 'primitive',
watermark: '',
description: 'The main title of the Dataset',
multiple: false,
isControlledVocabulary: false,
displayFormat: '',
isRequired: false,
displayOnCreate: true,
displayOrder: 0
}
}
}
]
}
}
Loading
Loading