From 8c21ff3013bccc4d580ed06e3eb7ec0de00b266b Mon Sep 17 00:00:00 2001 From: dgboss Date: Mon, 28 Oct 2024 09:02:49 -0700 Subject: [PATCH] Fix error when actuals are missing (#4045) --- web/src/api/moreCast2API.ts | 33 +------------ .../moreCast2/components/ActualCell.tsx | 48 +++++++++++++++++++ .../moreCast2/components/ColumnDefBuilder.tsx | 6 +-- .../components/GridComponentRenderer.tsx | 25 ++++++---- .../moreCast2/components/TabbedDataGrid.tsx | 2 +- .../moreCast2/components/ValidatedCell.tsx | 4 +- .../components/gridComponentRenderer.test.tsx | 24 +++++++++- .../features/moreCast2/saveForecast.test.ts | 6 ++- web/src/features/moreCast2/saveForecasts.ts | 3 +- web/src/features/moreCast2/util.test.ts | 9 ++-- web/src/features/moreCast2/util.ts | 3 +- 11 files changed, 109 insertions(+), 54 deletions(-) create mode 100644 web/src/features/moreCast2/components/ActualCell.tsx diff --git a/web/src/api/moreCast2API.ts b/web/src/api/moreCast2API.ts index c402ccaac..d89428797 100644 --- a/web/src/api/moreCast2API.ts +++ b/web/src/api/moreCast2API.ts @@ -1,8 +1,8 @@ import axios from 'api/axios' import { isEqual } from 'lodash' import { DateTime } from 'luxon' -import { MoreCast2ForecastRow, MoreCast2Row } from 'features/moreCast2/interfaces' -import { isForecastRowPredicate } from 'features/moreCast2/saveForecasts' +import { MoreCast2ForecastRow } from 'features/moreCast2/interfaces' + export enum ModelChoice { ACTUAL = 'ACTUAL', @@ -244,32 +244,3 @@ export async function fetchWeatherIndeterminates( return payload } - -export const mapMoreCast2RowsToIndeterminates = (rows: MoreCast2Row[]): WeatherIndeterminate[] => { - const mappedIndeterminates = rows.map(r => { - const isForecast = isForecastRowPredicate(r) - return { - id: r.id, - station_code: r.stationCode, - station_name: r.stationName, - determinate: isForecast ? WeatherDeterminate.FORECAST : WeatherDeterminate.ACTUAL, - latitude: r.latitude, - longitude: r.longitude, - utc_timestamp: r.forDate.toString(), - precipitation: isForecast ? r.precipForecast!.value : r.precipActual, - relative_humidity: isForecast ? r.rhForecast!.value : r.rhActual, - temperature: isForecast ? r.tempForecast!.value : r.tempActual, - wind_direction: isForecast ? r.windDirectionForecast!.value : r.windDirectionActual, - wind_speed: isForecast ? r.windSpeedForecast!.value : r.windSpeedActual, - fine_fuel_moisture_code: isForecast ? r.ffmcCalcForecast!.value : r.ffmcCalcActual, - duff_moisture_code: isForecast ? r.dmcCalcForecast!.value : r.dmcCalcActual, - drought_code: isForecast ? r.dcCalcForecast!.value : r.dcCalcActual, - initial_spread_index: isForecast ? r.isiCalcForecast!.value : r.isiCalcActual, - build_up_index: isForecast ? r.buiCalcForecast!.value : r.buiCalcActual, - fire_weather_index: isForecast ? r.fwiCalcForecast!.value : r.fwiCalcActual, - danger_rating: isForecast ? null : r.dgrCalcActual, - grass_curing: isForecast ? r.grassCuringForecast!.value : r.grassCuringActual - } - }) - return mappedIndeterminates -} diff --git a/web/src/features/moreCast2/components/ActualCell.tsx b/web/src/features/moreCast2/components/ActualCell.tsx new file mode 100644 index 000000000..63467777a --- /dev/null +++ b/web/src/features/moreCast2/components/ActualCell.tsx @@ -0,0 +1,48 @@ +import React, { useState } from 'react' +import { TextField, Tooltip } from '@mui/material' +import { theme } from 'app/theme' +import { GridRenderCellParams } from '@mui/x-data-grid-pro' + +interface ActualCellProps { + missingActual: boolean + value: Pick +} + +const MISSING_ACTUAL_MESSAGE = 'Actual not available from WF1.' + +const ActualCell = ({ missingActual, value }: ActualCellProps) => { + const [open, setOpen] = useState(false) + const handleClose = () => { + setOpen(false) + } + const handleOpen = () => { + if (missingActual) { + setOpen(true) + } + } + return ( + + + + ) +} + +export default React.memo(ActualCell) diff --git a/web/src/features/moreCast2/components/ColumnDefBuilder.tsx b/web/src/features/moreCast2/components/ColumnDefBuilder.tsx index 559d9d865..cddd32b8c 100644 --- a/web/src/features/moreCast2/components/ColumnDefBuilder.tsx +++ b/web/src/features/moreCast2/components/ColumnDefBuilder.tsx @@ -174,7 +174,7 @@ export class ColumnDefBuilder implements ColDefGenerator, ForecastColDefGenerato headerClassName: (params: Pick) => { return modelHeaderColorClass(params) }, - renderCell: (params: Pick) => { + renderCell: (params: Pick) => { return this.gridComponentRenderer.renderCellWith(params) }, renderHeader: (params: GridColumnHeaderParams) => { @@ -220,7 +220,7 @@ export class ColumnDefBuilder implements ColDefGenerator, ForecastColDefGenerato ? this.gridComponentRenderer.renderHeaderWith(params) : this.gridComponentRenderer.renderForecastHeaderWith(params, columnClickHandlerProps) }, - renderCell: (params: Pick) => { + renderCell: (params: Pick) => { return isCalcField ? this.gridComponentRenderer.renderCellWith(params) : this.gridComponentRenderer.renderForecastCellWith(params, field, validator) @@ -267,7 +267,7 @@ export class ColumnDefBuilder implements ColDefGenerator, ForecastColDefGenerato ? this.gridComponentRenderer.renderHeaderWith(params) : this.gridComponentRenderer.renderForecastHeaderWith(params, columnClickHandlerProps) }, - renderCell: (params: Pick) => { + renderCell: (params: Pick) => { return isCalcField ? this.gridComponentRenderer.renderCellWith(params) : this.gridComponentRenderer.renderForecastSummaryCellWith(params) diff --git a/web/src/features/moreCast2/components/GridComponentRenderer.tsx b/web/src/features/moreCast2/components/GridComponentRenderer.tsx index 7d596470a..1d9faabdd 100644 --- a/web/src/features/moreCast2/components/GridComponentRenderer.tsx +++ b/web/src/features/moreCast2/components/GridComponentRenderer.tsx @@ -8,7 +8,7 @@ import { GridValueSetterParams } from '@mui/x-data-grid-pro' import { ModelChoice, WeatherDeterminate } from 'api/moreCast2API' -import { createWeatherModelLabel, isBeforeToday, rowContainsActual } from 'features/moreCast2/util' +import { createWeatherModelLabel, isBeforeToday, isForecastRow, rowContainsActual } from 'features/moreCast2/util' import { GC_HEADER, PRECIP_HEADER, @@ -24,6 +24,7 @@ import { cloneDeep, isNumber } from 'lodash' import ForecastCell from 'features/moreCast2/components/ForecastCell' import ValidatedGrassCureForecastCell from '@/features/moreCast2/components/ValidatedGrassCureForecastCell' import ValidatedWindDirectionForecastCell from '@/features/moreCast2/components/ValidatedWindDirectionForecastCell' +import ActualCell from 'features/moreCast2/components/ActualCell' export const NOT_AVAILABLE = 'N/A' export const NOT_REPORTING = 'N/R' @@ -49,14 +50,20 @@ export class GridComponentRenderer { } return
{params.colDef.headerName}
} - public renderCellWith = (params: Pick) => ( - - ) + + public renderCellWith = (params: Pick) => { + if (!isForecastRow(params.row) && params.field.endsWith('Actual')) { + return + } + return ( + + ) + } public getActualField = (field: string) => { const actualField = field.replace('Forecast', 'Actual') diff --git a/web/src/features/moreCast2/components/TabbedDataGrid.tsx b/web/src/features/moreCast2/components/TabbedDataGrid.tsx index 30a23135d..2b50ce8c9 100644 --- a/web/src/features/moreCast2/components/TabbedDataGrid.tsx +++ b/web/src/features/moreCast2/components/TabbedDataGrid.tsx @@ -30,7 +30,7 @@ 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, isRequiredInputSet } from 'features/moreCast2/saveForecasts' +import { getRowsToSave, isForecastRowPredicate, 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' diff --git a/web/src/features/moreCast2/components/ValidatedCell.tsx b/web/src/features/moreCast2/components/ValidatedCell.tsx index ffd6c5a7b..320ac9801 100644 --- a/web/src/features/moreCast2/components/ValidatedCell.tsx +++ b/web/src/features/moreCast2/components/ValidatedCell.tsx @@ -12,7 +12,7 @@ interface ValidatedCellProps { value: Pick } -const ValidatedGrassCureForecastCell = ({ disabled, label, value, invalid, error }: ValidatedCellProps) => { +const ValidatedCell = ({ disabled, label, value, invalid, error }: ValidatedCellProps) => { const testTag = error ? 'validated-forecast-cell-error' : 'validated-forecast-cell' return ( @@ -51,4 +51,4 @@ const ValidatedGrassCureForecastCell = ({ disabled, label, value, invalid, error ) } -export default React.memo(ValidatedGrassCureForecastCell) +export default React.memo(ValidatedCell) diff --git a/web/src/features/moreCast2/components/gridComponentRenderer.test.tsx b/web/src/features/moreCast2/components/gridComponentRenderer.test.tsx index 53d61af3e..ec54935c2 100644 --- a/web/src/features/moreCast2/components/gridComponentRenderer.test.tsx +++ b/web/src/features/moreCast2/components/gridComponentRenderer.test.tsx @@ -14,6 +14,7 @@ import { ColumnClickHandlerProps } from 'features/moreCast2/components/TabbedDat import { DateTime } from 'luxon' import { Provider } from 'react-redux' import { vi } from 'vitest' +import { theme } from 'app/theme' describe('GridComponentRenderer', () => { const gridComponentRenderer = new GridComponentRenderer() @@ -132,8 +133,29 @@ describe('GridComponentRenderer', () => { expect(renderedCell).toBeDisabled() }) + it('should render N/R as ActualCell and have red border if no actual for row with forDate earlier than today', () => { + const field = 'tempActual' + const row = { [field]: NaN, forDate: DateTime.now().minus({ days: 2 }) } + const formattedValue = gridComponentRenderer.valueGetter({ row: row, value: NaN }, 1, field, 'Actual') + const { getByTestId } = render( + + {gridComponentRenderer.renderCellWith({ + row: row, + formattedValue: formattedValue, + field + })} + + ) + const renderedCell = getByTestId('actual-cell') + expect(renderedCell).toBeInTheDocument() + expect(renderedCell).toHaveStyle(`borderColor: ${theme.palette.error.main}`) + }) + it('should render the cell with the formatted value', () => { - const { getByRole } = render(gridComponentRenderer.renderCellWith({ formattedValue: 1 })) + const field = 'tempForecast' + const fieldActual = 'tempActual' + const row = { [field]: NaN, [fieldActual]: 2, forDate: DateTime.now() } + const { getByRole } = render(gridComponentRenderer.renderCellWith({ formattedValue: 1, field: fieldActual, row })) const renderedCell = getByRole('textbox') expect(renderedCell).toBeInTheDocument() diff --git a/web/src/features/moreCast2/saveForecast.test.ts b/web/src/features/moreCast2/saveForecast.test.ts index d3490312a..11c08f6b7 100644 --- a/web/src/features/moreCast2/saveForecast.test.ts +++ b/web/src/features/moreCast2/saveForecast.test.ts @@ -237,9 +237,11 @@ describe('saveForecasts', () => { }) describe('getRowsToSave', () => { it('should filter out rows with actuals', () => { + const yesterday = DateTime.now().minus({days: 1}) + const tomorrow = DateTime.now().plus({days: 1}) const res = getRowsToSave([ - buildForecast('1', mockForDate, 1, 'one', 1, 1), - buildForecastWithActuals('2', mockForDate, 2, 'two', 2, 2) + buildForecast('1', tomorrow, 1, 'one', 1, 1), + buildForecastWithActuals('2', yesterday, 2, 'two', 2, 2) ]) expect(res).toHaveLength(1) expect(res[0].id).toBe('1') diff --git a/web/src/features/moreCast2/saveForecasts.ts b/web/src/features/moreCast2/saveForecasts.ts index 57cb1eda0..67bf02bd3 100644 --- a/web/src/features/moreCast2/saveForecasts.ts +++ b/web/src/features/moreCast2/saveForecasts.ts @@ -1,3 +1,4 @@ +import { isBeforeToday } from 'features/moreCast2/util' import { ModelChoice } from 'api/moreCast2API' import { MoreCast2ForecastRow, MoreCast2Row, PredictionItem } from 'features/moreCast2/interfaces' import { isNil } from 'lodash' @@ -12,7 +13,7 @@ export const isForecastRowPredicate = (row: MoreCast2Row) => isNaN(row.grassCuringActual) export const getForecastRows = (rows: MoreCast2Row[]): MoreCast2Row[] => { - return rows ? rows.filter(isForecastRowPredicate) : [] + return rows ? rows.filter(row => isForecastRowPredicate(row) && !isBeforeToday(row.forDate)) : [] } export const getRowsToSave = (rows: MoreCast2Row[]): MoreCast2ForecastRow[] => { diff --git a/web/src/features/moreCast2/util.test.ts b/web/src/features/moreCast2/util.test.ts index f8b657145..f901d5dc3 100644 --- a/web/src/features/moreCast2/util.test.ts +++ b/web/src/features/moreCast2/util.test.ts @@ -27,6 +27,9 @@ const TEST_DATETIME = DateTime.fromISO(TEST_DATE) const YESTERDAY = DateTime.fromISO(TEST_DATE).plus({ days: -1 }) const TODAY = DateTime.fromISO(TEST_DATE) const TOMORROW = DateTime.fromISO(TEST_DATE).plus({ days: 1 }) +const REAL_YESTERDAY = DateTime.now().minus({day: 1}) +const REAL_TODAY = DateTime.now() +const REAL_TOMORROW = DateTime.now().plus({day: 1}) describe('createDateInterval', () => { it('should return array with single date when fromDate and toDate are the same', () => { @@ -505,9 +508,9 @@ describe('simulateFireWeatherIndices', () => { expect(result[0]).toBe(forecastRow) }) it('should simulate FWIs for all forecast rows', () => { - const forecastRowA = buildForecastRowWithIndices(1, YESTERDAY, 438, 89, 87) - const forecastRowB = buildValidForecastRow(1, TODAY) - const forecastRowC = buildValidForecastRow(1, TOMORROW) + const forecastRowA = buildForecastRowWithIndices(1, REAL_YESTERDAY, 438, 89, 87) + const forecastRowB = buildValidForecastRow(1, REAL_TODAY) + const forecastRowC = buildValidForecastRow(1, REAL_TOMORROW) // Change temp for last forecast so fire weather index values will be different than the previous day. forecastRowC.tempForecast!.value = 27 const result = simulateFireWeatherIndices([forecastRowA, forecastRowB, forecastRowC]) diff --git a/web/src/features/moreCast2/util.ts b/web/src/features/moreCast2/util.ts index 795f2b936..278d47f06 100644 --- a/web/src/features/moreCast2/util.ts +++ b/web/src/features/moreCast2/util.ts @@ -4,8 +4,9 @@ import { MoreCast2ForecastRow, MoreCast2Row } from 'features/moreCast2/interface import { StationGroupMember } from 'api/stationAPI' import { groupBy, isUndefined } from 'lodash' import { getDateTimeNowPST } from 'utils/date' -import { isForecastRowPredicate } from 'features/moreCast2/saveForecasts' import { bui, dc, dmc, ffmc, fwi, isi } from '@psu/cffdrs_ts' +import { isForecastRowPredicate } from 'features/moreCast2/saveForecasts' + export const parseForecastsHelper = ( forecasts: MoreCast2ForecastRecord[],