Skip to content

Commit

Permalink
Task/empty cell validation morecast (#3982)
Browse files Browse the repository at this point in the history
allows ModelChoice.NULL for default precip values
  • Loading branch information
conbrad authored Oct 2, 2024
1 parent 2873115 commit 43f30bf
Show file tree
Hide file tree
Showing 24 changed files with 625 additions and 280 deletions.
2 changes: 1 addition & 1 deletion .sonarcloud.properties
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ sonar.test.exclusions=*.feature
sonar.tests.inclusions=**/*.test.tsx

# Exclude duplication in fba tests due to many similar calculation numbers, ignore sample code as it's temporary, ignore sfms entrypoint, ignore util tests, ignore temporary fwi folder
sonar.cpd.exclusions=api/app/tests/fba_calc/*.py, api/app/weather_models/wind_direction_sample.py, web/src/features/moreCast2/util.test.ts, web/src/utils/fwi
sonar.cpd.exclusions=api/app/tests/fba_calc/*.py, api/app/weather_models/wind_direction_sample.py, web/src/features/moreCast2/util.test.ts, web/src/features/moreCast2/components/gridComponentRenderer.test.tsx, web/src/utils/fwi

# Encoding of the source code. Default is default system encoding
sonar.sourceEncoding=UTF-8
2 changes: 2 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"ffmc",
"fireweather",
"firezone",
"FWIs",
"GDPS",
"geoalchemy",
"GEOGCS",
Expand All @@ -85,6 +86,7 @@
"grib",
"gribs",
"HAINES",
"Hasher",
"hourlies",
"HRDPS",
"idir",
Expand Down
6 changes: 4 additions & 2 deletions web/src/features/moreCast2/components/EditInputCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ export const EditInputCell = (props: GridRenderEditCellParams) => {
const inputRef = useRef<HTMLInputElement | null>(null)
const dispatch: AppDispatch = useDispatch()

dispatch(setInputValid(isEmpty(error)))
useEffect(() => {
dispatch(setInputValid(isEmpty(error)))
}, [])

useEffect(() => {
if (hasFocus && inputRef.current) {
Expand Down Expand Up @@ -43,7 +45,7 @@ export const EditInputCell = (props: GridRenderEditCellParams) => {
}

return (
<InvalidCellToolTip error={error}>
<InvalidCellToolTip invalid={error}>
<TextField
data-testid="forecast-edit-cell"
type="number"
Expand Down
1 change: 1 addition & 0 deletions web/src/features/moreCast2/components/ForecastCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const ForecastCell = ({ disabled, label, showGreaterThan, showLessThan, value, v
if (showGreaterThan && showLessThan) {
throw Error('ForecastCell cannot show both greater than and less than icons at the same time.')
}

return (
<Grid container sx={{ justifyContent: 'center', alignItems: 'center' }}>
<Grid item xs={2}>
Expand Down
14 changes: 12 additions & 2 deletions web/src/features/moreCast2/components/GridComponentRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import ForecastHeader from 'features/moreCast2/components/ForecastHeader'
import { ColumnClickHandlerProps } from 'features/moreCast2/components/TabbedDataGrid'
import { cloneDeep, isNumber } from 'lodash'
import ForecastCell from 'features/moreCast2/components/ForecastCell'
import ValidatedForecastCell from '@/features/moreCast2/components/ValidatedForecastCell'
import ValidatedGrassCureForecastCell from '@/features/moreCast2/components/ValidatedGrassCureForecastCell'
import ValidatedWindDirectionForecastCell from '@/features/moreCast2/components/ValidatedWindDirectionForecastCell'

export const NOT_AVAILABLE = 'N/A'
export const NOT_REPORTING = 'N/R'
Expand Down Expand Up @@ -120,7 +121,16 @@ export class GridComponentRenderer {
// The grass curing 'forecast' field is rendered differently
if (isGrassField) {
return (
<ValidatedForecastCell
<ValidatedGrassCureForecastCell
disabled={isActual || isPreviousDate}
label={label}
value={params.formattedValue}
validator={validator}
/>
)
} else if (field.includes('windDirection')) {
return (
<ValidatedWindDirectionForecastCell
disabled={isActual || isPreviousDate}
label={label}
value={params.formattedValue}
Expand Down
8 changes: 4 additions & 4 deletions web/src/features/moreCast2/components/InvalidCellToolTip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ import { isEmpty } from 'lodash'
import { theme } from '@/app/theme'

export interface InvalidCellToolTipProps {
error: string
invalid: string
children: React.ReactNode
}

const InvalidCellToolTip = ({ error, children }: InvalidCellToolTipProps) => {
const InvalidCellToolTip = ({ invalid, children }: InvalidCellToolTipProps) => {
return (
<Tooltip
data-testid="validation-tooltip"
title={error}
open={!isEmpty(error)}
title={invalid}
open={!isEmpty(invalid)}
arrow
sx={{
'& .MuiTooltip-tooltip': {
Expand Down
2 changes: 1 addition & 1 deletion web/src/features/moreCast2/components/MoreCast2Column.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { GridPreProcessEditCellProps, GridValueFormatterParams } from '@mui/x-data-grid-pro'
import { GridValueFormatterParams } from '@mui/x-data-grid-pro'
import { DateTime } from 'luxon'
import {
ColDefGenerator,
Expand Down
15 changes: 10 additions & 5 deletions web/src/features/moreCast2/components/TabbedDataGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { AlertColor, Grid, List, Stack, Typography } from '@mui/material'
import { styled } from '@mui/material/styles'
import { useSelector, useDispatch } from 'react-redux'
import { AppDispatch } from '@/app/store'
import {
GridCellParams,
GridColDef,
Expand All @@ -20,7 +22,6 @@ import ForecastSummaryDataGrid from 'features/moreCast2/components/ForecastSumma
import SelectableButton from 'features/moreCast2/components/SelectableButton'
import { selectAllMoreCast2Rows, selectWeatherIndeterminatesLoading } from 'features/moreCast2/slices/dataSlice'
import React, { useEffect, useState } from 'react'
import { useSelector } from 'react-redux'
import { MoreCast2ForecastRow, MoreCast2Row, PredictionItem } from 'features/moreCast2/interfaces'
import { selectSelectedStations } from 'features/moreCast2/slices/selectedStationsSlice'
import { cloneDeep, groupBy, isEqual, isNull, isUndefined } from 'lodash'
Expand All @@ -29,14 +30,15 @@ import { ROLES } from 'features/auth/roles'
import { selectAuthentication } from 'app/rootReducer'
import { DateRange } from 'components/dateRangePicker/types'
import MoreCast2Snackbar from 'features/moreCast2/components/MoreCast2Snackbar'
import { isForecastRowPredicate, getRowsToSave, isForecastValid } from 'features/moreCast2/saveForecasts'
import { isForecastRowPredicate, getRowsToSave, isRequiredInputSet } from 'features/moreCast2/saveForecasts'
import MoreCast2DateRangePicker from 'features/moreCast2/components/MoreCast2DateRangePicker'
import { filterAllVisibleRowsForSimulation, filterRowsForSimulationFromEdited } from 'features/moreCast2/rowFilters'
import { fillStationGrassCuringForward, simulateFireWeatherIndices } from 'features/moreCast2/util'
import { MoreCastParams, theme } from 'app/theme'
import { MorecastDraftForecast } from 'features/moreCast2/forecastDraft'
import ResetForecastButton from 'features/moreCast2/components/ResetForecastButton'
import { getDateTimeNowPST } from 'utils/date'
import { setRequiredInputEmpty } from '@/features/moreCast2/slices/validInputSlice'

export interface ColumnClickHandlerProps {
colDef: GridColDef | null
Expand All @@ -56,7 +58,7 @@ export const Root = styled('div')({

const FORECAST_ERROR_MESSAGE = 'The forecast was not saved; an unexpected error occurred.'
const FORECAST_SAVED_MESSAGE = 'Forecast was successfully saved and sent to Wildfire One.'
const FORECAST_WARN_MESSAGE = 'Forecast not submitted. A forecast can only contain N/A values for the Wind Direction.'
const FORECAST_WARN_MESSAGE = 'Invalid forecast values, check highlighted cells for further information.'

const SHOW_HIDE_COLUMNS_LOCAL_STORAGE_KEY = 'showHideColumnsModel'

Expand All @@ -71,6 +73,7 @@ interface TabbedDataGridProps {
export type handleShowHideChangeType = (weatherParam: keyof MoreCastParams, columnName: string, value: boolean) => void

const TabbedDataGrid = ({ fromTo, setFromTo, fetchWeatherIndeterminates }: TabbedDataGridProps) => {
const dispatch: AppDispatch = useDispatch()
const selectedStations = useSelector(selectSelectedStations)
const loading = useSelector(selectWeatherIndeterminatesLoading)
const { roles, isAuthenticated } = useSelector(selectAuthentication)
Expand Down Expand Up @@ -445,8 +448,9 @@ const TabbedDataGrid = ({ fromTo, setFromTo, fetchWeatherIndeterminates }: Tabbe
}

const handleSaveClick = async () => {
if (isForecastValid(visibleRows)) {
const rowsToSave: MoreCast2ForecastRow[] = getRowsToSave(visibleRows)
const rowsToSave: MoreCast2ForecastRow[] = getRowsToSave(visibleRows)

if (isRequiredInputSet(rowsToSave)) {
const result = await submitMoreCastForecastRecords(rowsToSave)
if (result.success) {
setSnackbarMessage(FORECAST_SAVED_MESSAGE)
Expand All @@ -459,6 +463,7 @@ const TabbedDataGrid = ({ fromTo, setFromTo, fetchWeatherIndeterminates }: Tabbe
setSnackbarOpen(true)
}
} else {
dispatch(setRequiredInputEmpty({ empty: true }))
setSnackbarMessage(FORECAST_WARN_MESSAGE)
setSnackbarSeverity('warning')
setSnackbarOpen(true)
Expand Down
54 changes: 54 additions & 0 deletions web/src/features/moreCast2/components/ValidatedCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { theme } from '@/app/theme'
import InvalidCellToolTip from '@/features/moreCast2/components/InvalidCellToolTip'
import { TextField } from '@mui/material'
import { GridRenderCellParams } from '@mui/x-data-grid-pro'
import React from 'react'

interface ValidatedCellProps {
disabled: boolean
label: string
error: boolean
invalid: string
value: Pick<GridRenderCellParams, 'formattedValue'>
}

const ValidatedGrassCureForecastCell = ({ disabled, label, value, invalid, error }: ValidatedCellProps) => {
const testTag = error ? 'validated-forecast-cell-error' : 'validated-forecast-cell'
return (
<InvalidCellToolTip invalid={invalid}>
<TextField
data-testid={testTag}
disabled={disabled}
size="small"
label={label}
InputLabelProps={{
shrink: true
}}
sx={{
'& .MuiOutlinedInput-root': {
backgroundColor: `${theme.palette.common.white}`,
'& fieldset': {
borderColor: error ? theme.palette.error.main : '#737373',
borderWidth: '2px'
},
'&:hover fieldset': {
borderColor: error ? theme.palette.error.main : '#737373'
},
'&.Mui-focused fieldset': {
borderColor: error ? theme.palette.error.main : '#737373',
borderWidth: '2px'
}
},
'& .Mui-disabled': {
'& fieldset': {
borderWidth: '1px'
}
}
}}
value={value}
></TextField>
</InvalidCellToolTip>
)
}

export default React.memo(ValidatedGrassCureForecastCell)
46 changes: 8 additions & 38 deletions web/src/features/moreCast2/components/ValidatedForecastCell.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React from 'react'
import { TextField } from '@mui/material'
import { GridRenderCellParams } from '@mui/x-data-grid-pro'
import { theme } from 'app/theme'
import InvalidCellToolTip from '@/features/moreCast2/components/InvalidCellToolTip'
import { selectMorecastRequiredInputEmpty } from '@/features/moreCast2/slices/validInputSlice'
import { useSelector } from 'react-redux'
import { isNil } from 'lodash'
import ValidatedCell from '@/features/moreCast2/components/ValidatedCell'

interface ValidatedForecastCellProps {
disabled: boolean
Expand All @@ -12,41 +13,10 @@ interface ValidatedForecastCellProps {
}

const ValidatedForecastCell = ({ disabled, label, value, validator }: ValidatedForecastCellProps) => {
const error = validator ? validator(value as string) : ''
return (
<InvalidCellToolTip error={error}>
<TextField
disabled={disabled}
size="small"
label={label}
InputLabelProps={{
shrink: true
}}
sx={{
'& .MuiOutlinedInput-root': {
backgroundColor: `${theme.palette.common.white}`,
'& fieldset': {
borderColor: error ? theme.palette.error.main : '#737373',
borderWidth: '2px'
},
'&:hover fieldset': {
borderColor: error ? theme.palette.error.main : '#737373'
},
'&.Mui-focused fieldset': {
borderColor: error ? theme.palette.error.main : '#737373',
borderWidth: '2px'
}
},
'& .Mui-disabled': {
'& fieldset': {
borderWidth: '1px'
}
}
}}
value={value}
></TextField>
</InvalidCellToolTip>
)
const isRequiredInputEmpty = useSelector(selectMorecastRequiredInputEmpty)
const invalid = validator ? validator(value as string) : ''
const error = (isRequiredInputEmpty.empty && (value as string) === '') || isNil(value) || invalid !== ''
return <ValidatedCell disabled={disabled} label={label} value={value} error={error} invalid={invalid} />
}

export default React.memo(ValidatedForecastCell)
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react'
import { GridRenderCellParams } from '@mui/x-data-grid-pro'
import ValidatedCell from '@/features/moreCast2/components/ValidatedCell'
import { Box } from '@mui/material'

interface ValidatedGrassCureForecastCellProps {
disabled: boolean
label: string
value: Pick<GridRenderCellParams, 'formattedValue'>
validator?: (value: string) => string
}

const ValidatedGrassCureForecastCell = ({ disabled, label, value, validator }: ValidatedGrassCureForecastCellProps) => {
const error = validator ? validator(value as string) : ''
return (
<Box data-testid="validated-gc-forecast-cell">
<ValidatedCell disabled={disabled} label={label} error={error !== ''} invalid={error} value={value} />
</Box>
)
}

export default React.memo(ValidatedGrassCureForecastCell)
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Grid } from '@mui/material'
import { GridRenderCellParams } from '@mui/x-data-grid-pro'
import ValidatedCell from '@/features/moreCast2/components/ValidatedCell'

interface ForecastCellProps {
disabled: boolean
label: string
value: Pick<GridRenderCellParams, 'formattedValue'>
validator?: (value: string) => string
}

const ValidatedWindDirectionForecastCell = ({ disabled, label, value, validator }: ForecastCellProps) => {
const error = validator ? validator(value as string) : ''

return (
<Grid
container
sx={{ justifyContent: 'center', alignItems: 'center' }}
data-testid="validated-winddir-forecast-cell"
>
<Grid item xs={8}>
<ValidatedCell disabled={disabled} label={label} value={value} error={error !== ''} invalid={error} />
</Grid>
</Grid>
)
}

export default ValidatedWindDirectionForecastCell
Loading

0 comments on commit 43f30bf

Please sign in to comment.