Skip to content

Commit

Permalink
fix: remote markdown url validation and suggestions
Browse files Browse the repository at this point in the history
  • Loading branch information
dmijatovic committed Oct 18, 2024
1 parent 9652fb5 commit 502d5a2
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 121 deletions.
12 changes: 9 additions & 3 deletions frontend/components/layout/PageErrorMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all)
// SPDX-FileCopyrightText: 2022 dv4all
// SPDX-FileCopyrightText: 2024 Dusan Mijatovic (Netherlands eScience Center)
// SPDX-FileCopyrightText: 2024 Netherlands eScience Center
//
// SPDX-License-Identifier: Apache-2.0

import ContentInTheMiddle from './ContentInTheMiddle'

export default function PageErrorMessage({status = 500, message = 'Failed to process you request'}: {
message:string, status?:number
export default function PageErrorMessage({status = 500, message = 'Failed to process you request',children}: {
message:string, status?:number, children?:any
}) {
return (
<ContentInTheMiddle>
<section className="flex justify-center items-center text-secondary">
<h1 className="border-r-2 px-4 font-medium">{status}</h1>
<p className="px-4 tracking-wider">{message}</p>
{children ?
children
:
<p className="px-4 tracking-wider">{message}</p>
}
</section>
</ContentInTheMiddle>
)
Expand Down
65 changes: 1 addition & 64 deletions frontend/components/software/edit/editSoftwareConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,70 +100,7 @@ export const contributorInformation = {
infoLink: '/documentation/users/adding-software/#contributors',
label: 'Import contributors',
message: (doi: string) => `Import contributors from datacite.org using DOI ${doi}`
},
// We use default config of AggregatedPersonModal
// Dusan, 2024-09-12
// is_contact_person: {
// label: 'Contact person',
// help: 'Is this contributor the main contact person?'
// },
// given_names: {
// label: 'First name / Given name(s)',
// help: 'One or more given names',
// validation: {
// required: 'First name is required',
// minLength: {value: 1, message: 'Minimum length is 1'},
// maxLength: {value: 200, message: 'Maximum length is 200'},
// }
// },
// family_names: {
// label: 'Last name / Family name(s)',
// help: 'Include "de/van/van den ...etc."',
// validation: {
// required: 'Family name is required',
// minLength: {value: 2, message: 'Minimum length is 2'},
// maxLength: {value: 200, message: 'Maximum length is 200'},
// }
// },
// email_address: {
// label: 'Email',
// help: 'Contact person should have an email',
// validation: (required:boolean) => ({
// required: required ? 'Contact person should have an email' : false,
// minLength: {value: 5, message: 'Minimum length is 5'},
// maxLength: {value: 200, message: 'Maximum length is 200'},
// pattern: {
// value: /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
// message: 'Invalid email address'
// }
// })
// },
// affiliation: {
// label: 'Affiliation',
// help: 'Select or type in the current affiliation?',
// validation: {
// minLength: {value: 2, message: 'Minimum length is 2'},
// maxLength: {value: 200, message: 'Maximum length is 200'},
// }
// },
// role: {
// label: 'Role',
// help: 'For this software',
// validation: {
// minLength: {value: 2, message: 'Minimum length is 2'},
// maxLength: {value: 200, message: 'Maximum length is 200'},
// }
// },
// orcid: {
// label: 'ORCID',
// help: '16 digits, pattern 0000-0000-0000-0000',
// validation: {
// pattern: {
// value: /^\d{4}-\d{4}-\d{4}-\d{3}[0-9X]$/,
// message: 'Invalid pattern, not a 0000-0000-0000-0000'
// }
// }
// }
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
// SPDX-License-Identifier: Apache-2.0

import {useState,useEffect} from 'react'
import {useWatch,useFormState} from 'react-hook-form'
import {useWatch,useFormState, useFormContext} from 'react-hook-form'

import {EditSoftwareItem} from '~/types/SoftwareTypes'
import {getRemoteMarkdown} from '~/utils/getSoftware'
import {useDebounceValid} from '~/utils/useDebounce'
import {apiRemoteMarkdown} from '~/utils/getSoftware'
import ReactMarkdownWithSettings from '~/components/layout/ReactMarkdownWithSettings'
import PageErrorMessage from '~/components/layout/PageErrorMessage'
import ContentLoader from '~/components/layout/ContentLoader'
Expand All @@ -26,17 +26,18 @@ type AutosaveRemoteMarkdownProps = {
onSaveField: ({name,value}: OnSaveProps<EditSoftwareItem>) => void
}

export default function AutosaveRemoteMarkdown({control,rules,options,onSaveField}: AutosaveRemoteMarkdownProps) {
// watch for change
const markdownUrl = useWatch({control, name: options.name})
export default function AutosaveRemoteMarkdown({rules,options,onSaveField}: AutosaveRemoteMarkdownProps) {
const {control, setError} = useFormContext()
const {errors} = useFormState({control})
const debouncedUrl = useDebounceValid(markdownUrl,errors[options.name],1000)
const markdownUrl = useWatch({control, name: options.name})
const debouncedUrl = useDebounceValid(markdownUrl,errors[options.name],700)
const [loading, setLoading] = useState(false)
const [state, setState] = useState<{
markdown: string | null,
error: {
status: number | null,
message: string | null
message: string | null,
rawUrl?: string
}
}>({
markdown: null,
Expand All @@ -45,63 +46,53 @@ export default function AutosaveRemoteMarkdown({control,rules,options,onSaveFiel
message: null
}
})
// listen for error
let error:any
if (errors.hasOwnProperty(options.name) === true) {
error = errors[options.name]
}

// console.group(`AutosaveRemoteMarkdownProps...${options.name}`)
// console.log('markdownUrl...',markdownUrl)
// console.log('debouncedUrl...',debouncedUrl)
// console.log('errors...', errors)
// console.log('error...', error)
// console.log('state...', state)
// console.groupEnd()

useEffect(() => {
let abort = false
const getMarkdown=async(url:string)=>{
setLoading(true)
const markdown = await getRemoteMarkdown(url)
const resp = await apiRemoteMarkdown(url)
// exit on abort
if (abort) return
if (typeof markdown === 'string') {
if (resp.status===200) {
// debugger
setState({
markdown,
markdown: resp.message,
error: {
status: null,
message: null
}
})
} else if (typeof markdown === 'object'){
} else {
// create error
setState({
markdown: null,
error: {
...markdown
...resp
}
})
// set error to input form
setError(options.name,{
type:'custom',
message: `${resp.message} ${resp.rawUrl ? `Try ${resp.rawUrl}` : ''}`
})
}
setLoading(false)
}
// if there is markdownUrl value
if (debouncedUrl &&
debouncedUrl.length > 9) {
if (debouncedUrl && debouncedUrl.length > 9) {
if (abort) return
getMarkdown(debouncedUrl)
} else if (!debouncedUrl) {
if (abort) return
setLoading(false)
setState({
markdown: '',
error: {
status: 200,
message: 'Waiting for input'
}
})
}
return ()=>{abort=true}
}, [debouncedUrl])
}, [debouncedUrl,options.name,setError])

function renderContent() {
if (loading) {
Expand All @@ -112,7 +103,15 @@ export default function AutosaveRemoteMarkdown({control,rules,options,onSaveFiel
<PageErrorMessage
status={state.error?.status ?? undefined}
message={state.error?.message ?? 'Server error'}
/>
>
<div className="px-4 ">
<span className="text-error">{state.error?.message ?? 'Incorrect link'}</span><br/>
{ state.error?.rawUrl ?
<div><span className="font-medium">Suggestion:</span> <a href={state.error?.rawUrl} target="_blank">{state.error?.rawUrl}</a></div>
: null
}
</div>
</PageErrorMessage>
)
}
if (state.markdown) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center)
// SPDX-FileCopyrightText: 2023 - 2024 Dusan Mijatovic (Netherlands eScience Center)
// SPDX-FileCopyrightText: 2023 - 2024 Netherlands eScience Center
// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) (dv4all)
// SPDX-FileCopyrightText: 2023 Netherlands eScience Center
// SPDX-FileCopyrightText: 2023 dv4all
//
// SPDX-License-Identifier: Apache-2.0

import {fireEvent, render, screen, waitFor, waitForElementToBeRemoved,act} from '@testing-library/react'
import {fireEvent, render, screen, waitFor, waitForElementToBeRemoved} from '@testing-library/react'

import {WithAppContext, mockSession} from '~/utils/jest/WithAppContext'
import {WithFormContext} from '~/utils/jest/WithFormContext'
Expand All @@ -20,8 +20,13 @@ jest.mock('./patchSoftwareTable', () => ({
}))

const mockGetRemoteMarkdown = jest.fn(props => Promise.resolve('Remote markdown'))
const mockApiRemoteMarkdown = jest.fn(props => Promise.resolve({
status:200,
message: 'Remote markdown'
}))
jest.mock('~/utils/getSoftware', () => ({
getRemoteMarkdown: jest.fn(props=>mockGetRemoteMarkdown(props))
getRemoteMarkdown: jest.fn(props=>mockGetRemoteMarkdown(props)),
apiRemoteMarkdown: jest.fn(props=>mockApiRemoteMarkdown(props))
}))

beforeEach(() => {
Expand Down Expand Up @@ -79,8 +84,12 @@ it('shows loaded description_url', async() => {
description_url: 'https://github.com/project/README.md'
}
// mock remote api response
const expectedMarkdown = 'Remote markdown for testing'
mockGetRemoteMarkdown.mockResolvedValueOnce(expectedMarkdown)
const expectedMarkdown = {
status:200,
message:'Remote markdown for testing'
}

mockApiRemoteMarkdown.mockResolvedValueOnce(expectedMarkdown)

render(
<WithAppContext options={{session: mockSession}}>
Expand All @@ -94,7 +103,7 @@ it('shows loaded description_url', async() => {

// expect document URL
const documentUrl = screen.getByRole('radio', {
name: 'Document URL'
name: 'Markdown URL'
})
expect(documentUrl).toBeChecked()
// select document_url
Expand All @@ -103,7 +112,7 @@ it('shows loaded description_url', async() => {
// wait loader to be removed
await waitForElementToBeRemoved(screen.getByRole('progressbar'))
// validate remote markdown response
screen.getByText(expectedMarkdown)
screen.getByText(expectedMarkdown.message)
})

it('saves custom markdown', async() => {
Expand Down Expand Up @@ -194,7 +203,7 @@ it('saves remote markdown', async() => {

// check remote markdown
const remoteUrl = screen.getByRole('radio', {
name: 'Document URL'
name: 'Markdown URL'
})
fireEvent.click(remoteUrl)
expect(remoteUrl).toBeChecked()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@
//
// SPDX-License-Identifier: Apache-2.0

import {FocusEventHandler} from 'react'
import Radio from '@mui/material/Radio'
import RadioGroup from '@mui/material/RadioGroup'
import FormControlLabel from '@mui/material/FormControlLabel'
import {useController, useFormContext} from 'react-hook-form'

import MarkdownInputWithPreview from '../../../form/MarkdownInputWithPreview'
import EditSectionTitle from '../../../layout/EditSectionTitle'
import {softwareInformation as config} from '../editSoftwareConfig'
import {useSession} from '~/auth'
import MarkdownInputWithPreview from '~/components/form/MarkdownInputWithPreview'
import EditSectionTitle from '~/components/layout/EditSectionTitle'
import useSnackbar from '~/components/snackbar/useSnackbar'
import {useController, useFormContext} from 'react-hook-form'
import {softwareInformation as config} from '../editSoftwareConfig'
import {patchSoftwareTable} from './patchSoftwareTable'
import AutosaveRemoteMarkdown from './AutosaveRemoteMarkdown'
import {FocusEventHandler} from 'react'

type SaveInfo = {
name: string,
Expand Down Expand Up @@ -98,12 +98,6 @@ export default function AutosaveSoftwareMarkdown() {
token
})

// console.group('AutosaveSoftwareMarkdown.saveSoftwareInfo')
// console.log('saved...', name)
// console.log('data...', data)
// console.log('status...', resp?.status)
// console.groupEnd()

if (resp?.status !== 200) {
showErrorMessage(`Failed to save ${name}. ${resp?.message}`)
} else {
Expand Down Expand Up @@ -194,7 +188,7 @@ export default function AutosaveSoftwareMarkdown() {
/>
<div className="py-2"></div>
<FormControlLabel
label="Document URL"
label="Markdown URL"
value="link"
defaultValue={'link'}
control={<Radio />}
Expand Down
Loading

0 comments on commit 502d5a2

Please sign in to comment.