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

Morecast: Empty Cell validation #3957

Merged
merged 19 commits into from
Sep 26, 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
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 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 { 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 @@

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 @@
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 handleSaveClick = async () => {
if (isForecastValid(visibleRows)) {
const rowsToSave: MoreCast2ForecastRow[] = getRowsToSave(visibleRows)
const rowsToSave: MoreCast2ForecastRow[] = getRowsToSave(visibleRows)

Check warning on line 451 in web/src/features/moreCast2/components/TabbedDataGrid.tsx

View check run for this annotation

Codecov / codecov/patch

web/src/features/moreCast2/components/TabbedDataGrid.tsx#L451

Added line #L451 was not covered by tests

if (isRequiredInputSet(rowsToSave)) {
const result = await submitMoreCastForecastRecords(rowsToSave)
if (result.success) {
setSnackbarMessage(FORECAST_SAVED_MESSAGE)
Expand All @@ -459,6 +463,7 @@
setSnackbarOpen(true)
}
} else {
dispatch(setRequiredInputEmpty({ empty: true }))

Check warning on line 466 in web/src/features/moreCast2/components/TabbedDataGrid.tsx

View check run for this annotation

Codecov / codecov/patch

web/src/features/moreCast2/components/TabbedDataGrid.tsx#L466

Added line #L466 was not covered by tests
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
Loading