Skip to content

Commit

Permalink
Add error handling to download interface
Browse files Browse the repository at this point in the history
  • Loading branch information
jochenklar committed Nov 12, 2024
1 parent 9cd08ac commit 28f7e93
Show file tree
Hide file tree
Showing 13 changed files with 173 additions and 83 deletions.
10 changes: 10 additions & 0 deletions config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,16 @@
}
]

DOWNLOAD_ERRORS = {
'bbox': ['Please enter a valid bounding box.'],
'point': ['Please enter a valid point.'],
'country': ['Please select a country.'],
'mask': ['Please select a valid NetCDF file.'],
'shape': ['Please select a valid Shapefile or GeoJSON file.'],
'var': ['Please enter a valid name for a variable.'],
'layer': ['Please enter a layer.']
}

RESTRICTED_MESSAGES = {}
RESTRICTED_DEFAULT_MESSAGE = 'Please contact <a href="mailto:[email protected]">[email protected]</a>' \
' if you need access to the dataset.'
3 changes: 2 additions & 1 deletion isimip_data/core/viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def get_queryset(self):
} for key in [
'METADATA_PAGE_SIZE',
'METADATA_MAX_COUNT',
'DOWNLOAD_OPERATIONS_HELP'
'DOWNLOAD_OPERATIONS_HELP',
'DOWNLOAD_ERRORS'
]
]
109 changes: 69 additions & 40 deletions isimip_data/download/assets/js/components/Form.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'
import PropTypes from 'prop-types'
import { get, isEmpty, isNil, isUndefined } from 'lodash'
import { get, isEmpty, isNil, isNumber, isUndefined } from 'lodash'

import { useLsState } from 'isimip_data/core/assets/js/hooks/ls'
import { useSettingsQuery } from 'isimip_data/core/assets/js/hooks/queries'
Expand Down Expand Up @@ -46,46 +46,75 @@ const Form = ({ files, setJob }) => {
const handleSubmit = (event) => {
event.preventDefault()

const uploads = []
const data = {
paths,
operations: operations.reduce((operations, operation) => {
const { mask, shape, ...values } = operation

switch (operation.operation) {
case 'mask_mask':
uploads.push(mask)
return [...operations, { ...values, mask: mask.name }]

case 'mask_shape':
uploads.push(shape)
return [
...operations,
{
operation: 'create_mask',
shape: shape.name,
mask: shape.name + '.nc'
},
{
operation: 'mask_mask',
mask: shape.name + '.nc',
var: `m_${values.layer}`
}
]

default:
return [...operations, values]
}
}, [])
}

mutation.mutate({
url: settings.FILES_API_URL,
data,
uploads,
setErrors,
setJob
let errors = {}
operations.forEach((operation) => {
if (!isUndefined(operation.bbox) && operation.bbox.some(v => !isNumber(v))) {
errors = { ...errors, bbox: settings.DOWNLOAD_ERRORS.bbox }
}
if (!isUndefined(operation.point) && operation.point.some(v => !isNumber(v))) {
errors = { ...errors, point: settings.DOWNLOAD_ERRORS.point }
}
if (!isUndefined(operation.country) && isEmpty(operation.country)) {
errors = { ...errors, country: settings.DOWNLOAD_ERRORS.country }
}
if (!isUndefined(operation.mask) && isNil(operation.mask)) {
errors = { ...errors, mask: settings.DOWNLOAD_ERRORS.mask }
}
if (!isUndefined(operation.shape) && isNil(operation.shape)) {
errors = { ...errors, shape: settings.DOWNLOAD_ERRORS.shape }
}
if (!isUndefined(operation.var) && isEmpty(operation.var)) {
errors = { ...errors, var: settings.DOWNLOAD_ERRORS.var }
}
if (!isUndefined(operation.layer) && isNumber(operation.layer)) {
errors = { ...errors, layer: settings.DOWNLOAD_ERRORS.layer }
}
})

if (isEmpty(errors)) {
const uploads = []
const data = {
paths,
operations: operations.reduce((operations, operation) => {
const { mask, shape, ...values } = operation

switch (operation.operation) {
case 'mask_mask':
uploads.push(mask)
return [...operations, { ...values, mask: mask.name }]

case 'mask_shape':
uploads.push(shape)
return [
...operations,
{
operation: 'create_mask',
shape: shape.name,
mask: shape.name + '.nc'
},
{
operation: 'mask_mask',
mask: shape.name + '.nc',
var: `m_${values.layer}`
}
]

default:
return [...operations, values]
}
}, [])
}

mutation.mutate({
url: settings.FILES_API_URL,
data,
uploads,
setErrors,
setJob
})
} else {
setErrors(errors)
}
}

return settings && (
Expand Down
9 changes: 3 additions & 6 deletions isimip_data/download/assets/js/components/form/Operation.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ import Mean from './widgets/Mean'
import Point from './widgets/Point'
import Var from './widgets/Var'


const Operation = ({ operation, index, isLast, values, errors, updateOperation, removeOperation }) => {
const error = []

return (
<div className="card mb-2 operation">
<div className="card-body">
Expand All @@ -35,7 +32,7 @@ const Operation = ({ operation, index, isLast, values, errors, updateOperation,
!isUndefined(values.bbox) && (
<BBox
bbox={values.bbox}
errors={errors}
errors={errors.bbox}
onChange={bbox => updateOperation(index, {...values, bbox})}
/>
)
Expand All @@ -44,7 +41,7 @@ const Operation = ({ operation, index, isLast, values, errors, updateOperation,
!isUndefined(values.point) && (
<Point
point={values.point}
errors={errors}
errors={errors.point}
onChange={point => updateOperation(index, {...values, point})}
/>
)
Expand All @@ -53,7 +50,7 @@ const Operation = ({ operation, index, isLast, values, errors, updateOperation,
!isUndefined(values.country) && (
<Country
country={values.country}
errors={errors}
errors={errors.country}
onChange={country => updateOperation(index, {...values, country})}
/>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const Operations = ({ operationsConfig, operationsHelp, operations, errors, setO
index={index}
isLast={index == operations.length - 1}
values={values}
errors={[]}
errors={errors}
updateOperation={updateOperation}
removeOperation={removeOperation}
/>
Expand Down
53 changes: 30 additions & 23 deletions isimip_data/download/assets/js/components/form/widgets/BBox.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React from 'react'
import PropTypes from 'prop-types'
import { isEmpty, uniqueId } from 'lodash'

import Errors from './Errors'

const BBox = ({ bbox, errors, onChange }) => {
const [ west, east, south, north ] = bbox

Expand All @@ -20,29 +22,34 @@ const BBox = ({ bbox, errors, onChange }) => {
}

return <>
<div className="col-lg-2">
<label className="mb-0" htmlFor={westId}>West</label>
<input className={'form-control download-form-input-bbox mb-2 ' + (!isEmpty(errors) && 'is-invalid')}
type="number" id={westId} placeholder="West" min="-180" max="180" step="any"
value={west} onChange={event => handleChange(event, 0)} onWheel={handleWheel} />
</div>
<div className="col-lg-2">
<label className="mb-0" htmlFor={eastId}>East</label>
<input className={'form-control download-form-input-bbox mb-2 ' + (!isEmpty(errors) && 'is-invalid')}
type="number" id={eastId} placeholder="East" min="-180" max="180" step="any"
value={east} onChange={event => handleChange(event, 1)} onWheel={handleWheel} />
</div>
<div className="col-lg-2">
<label className="mb-0" htmlFor={southId}>South</label>
<input className={'form-control download-form-input-bbox mb-2 ' + (!isEmpty(errors) && 'is-invalid')}
type="number" id={southId} placeholder="South" min="-90" max="90" step="any"
value={south} onChange={event => handleChange(event, 2)} onWheel={handleWheel} />
</div>
<div className="col-lg-2">
<label className="mb-0" htmlFor={northId}>North</label>
<input className={'form-control download-form-input-bbox mb-2 ' + (!isEmpty(errors) && 'is-invalid')}
type="number" id={northId} placeholder="North" min="-90" max="90" step="any"
value={north} onChange={event => handleChange(event, 3)} onWheel={handleWheel} />
<div className="col-lg-8">
<div className="row">
<div className="col-lg-3">
<label className="mb-0" htmlFor={westId}>West</label>
<input className={'form-control download-form-input-bbox mb-2 ' + (!isEmpty(errors) && 'is-invalid')}
type="number" id={westId} placeholder="West" min="-180" max="180" step="any"
value={west} onChange={event => handleChange(event, 0)} onWheel={handleWheel} />
</div>
<div className="col-lg-3">
<label className="mb-0" htmlFor={eastId}>East</label>
<input className={'form-control download-form-input-bbox mb-2 ' + (!isEmpty(errors) && 'is-invalid')}
type="number" id={eastId} placeholder="East" min="-180" max="180" step="any"
value={east} onChange={event => handleChange(event, 1)} onWheel={handleWheel} />
</div>
<div className="col-lg-3">
<label className="mb-0" htmlFor={southId}>South</label>
<input className={'form-control download-form-input-bbox mb-2 ' + (!isEmpty(errors) && 'is-invalid')}
type="number" id={southId} placeholder="South" min="-90" max="90" step="any"
value={south} onChange={event => handleChange(event, 2)} onWheel={handleWheel} />
</div>
<div className="col-lg-3">
<label className="mb-0" htmlFor={northId}>North</label>
<input className={'form-control download-form-input-bbox mb-2 ' + (!isEmpty(errors) && 'is-invalid')}
type="number" id={northId} placeholder="North" min="-90" max="90" step="any"
value={north} onChange={event => handleChange(event, 3)} onWheel={handleWheel} />
</div>
</div>
<Errors errors={errors} />
</div>
</>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { isEmpty, uniqueId } from 'lodash'

import { useCountriesQuery } from '../../../hooks/queries'

import Errors from './Errors'

const Country = ({ country, errors, onChange }) => {
const { data: countries } = useCountriesQuery()

Expand All @@ -20,6 +22,7 @@ const Country = ({ country, errors, onChange }) => {
})
}
</select>
<Errors errors={errors} />
</div>
</>
}
Expand Down
23 changes: 23 additions & 0 deletions isimip_data/download/assets/js/components/form/widgets/Errors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React, { useState } from 'react'
import PropTypes from 'prop-types'
import { isEmpty } from 'lodash'

const Errors = ({ errors }) => {
if (isEmpty(errors)) {
return null
} else {
return (
<ul className="text-danger list-unstyled mb-2">
{
errors.map((error, errorIndex) => <li>{error}</li>)
}
</ul>
)
}
}

Errors.propTypes = {
errors: PropTypes.array
}

export default Errors
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React from 'react'
import PropTypes from 'prop-types'
import { isEmpty, uniqueId } from 'lodash'

import Errors from './Errors'

const Layer = ({ value, errors, onChange }) => {
const id = uniqueId('download-form-input-layer-')

Expand All @@ -11,6 +13,7 @@ const Layer = ({ value, errors, onChange }) => {
<input className={'form-control mb-2 ' + (!isEmpty(errors) && 'is-invalid')}
type="number" id={id} placeholder="Layer"
value={value} onChange={event => onChange(event.target.value)} />
<Errors errors={errors} />
</div>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import PropTypes from 'prop-types'
import { useDropzone } from 'react-dropzone'
import { isEmpty, isNil, uniqueId } from 'lodash'

import Errors from './Errors'

const Mask = ({ file, accept, errors, onChange }) => {
const fileId = uniqueId('download-form-input-file-')

Expand Down Expand Up @@ -49,9 +51,10 @@ const Mask = ({ file, accept, errors, onChange }) => {
</div>
{
dropzoneError && (
<div class="invalid-feedback">{dropzoneError}</div>
<div class="text-danger">{dropzoneError}</div>
)
}
<Errors errors={errors} />
</div>
)
}
Expand Down
29 changes: 18 additions & 11 deletions isimip_data/download/assets/js/components/form/widgets/Point.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React from 'react'
import PropTypes from 'prop-types'
import { isEmpty, uniqueId } from 'lodash'

import Errors from './Errors'

const Point = ({ point, errors, onChange }) => {
const [ lat, lon ] = point

Expand All @@ -18,17 +20,22 @@ const Point = ({ point, errors, onChange }) => {
}

return <>
<div className="col-lg-4">
<label className="mb-0" htmlFor={latId}>Latitude</label>
<input className={'form-control download-form-input-point mb-2 ' + (!isEmpty(errors) && 'is-invalid')}
type="number" id={latId} placeholder="Latitude" min="-90" max="90" step="any"
value={lat} onChange={event => handleChange(event, 0)} />
</div>
<div className="col-lg-4">
<label className="mb-0" htmlFor={lonId}>Longitude</label>
<input className={'form-control download-form-input-point mb-2 ' + (!isEmpty(errors) && 'is-invalid')}
type="number" id={lonId} placeholder="Longitude" min="-180" max="180" step="any"
value={lon} onChange={event => handleChange(event, 1)} />
<div className="col-lg-8">
<div className="row">
<div className="col-lg-6">
<label className="mb-0" htmlFor={latId}>Latitude</label>
<input className={'form-control download-form-input-point mb-2 ' + (!isEmpty(errors) && 'is-invalid')}
type="number" id={latId} placeholder="Latitude" min="-90" max="90" step="any"
value={lat} onChange={event => handleChange(event, 0)} />
</div>
<div className="col-lg-6">
<label className="mb-0" htmlFor={lonId}>Longitude</label>
<input className={'form-control download-form-input-point mb-2 ' + (!isEmpty(errors) && 'is-invalid')}
type="number" id={lonId} placeholder="Longitude" min="-180" max="180" step="any"
value={lon} onChange={event => handleChange(event, 1)} />
</div>
</div>
<Errors errors={errors} />
</div>
</>
}
Expand Down
Loading

0 comments on commit 28f7e93

Please sign in to comment.