Skip to content
This repository has been archived by the owner on Dec 9, 2020. It is now read-only.

Commit

Permalink
Show hourly data (#126)
Browse files Browse the repository at this point in the history
- Showing hourly data.
- Changed where branch name check happens - now in husky.
- Rounding on RH and wind direction.
  • Loading branch information
Sybrand authored May 21, 2020
1 parent 8ccec4f commit e0a4dbf
Show file tree
Hide file tree
Showing 11 changed files with 310 additions and 16 deletions.
18 changes: 18 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"@types/webpack-env": "^1.15.1",
"axios": "^0.19.2",
"clsx": "^1.1.0",
"moment": "^2.26.0",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-redux": "^7.2.0",
Expand Down Expand Up @@ -70,7 +71,7 @@
},
"husky": {
"hooks": {
"pre-push": "npm run lint & npm run test:ci"
"pre-push": ".githooks/pre-push & npm run lint & npm run test:ci"
}
}
}
}
37 changes: 37 additions & 0 deletions src/api/hourliesAPI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Station } from 'api/stationAPI'
import axios from 'api/axios'

interface HourlyReading {
datetime: string
temperature: number
relative_humidity: number
wind_speed: number
wind_direction: number
barometric_pressure: number
precipitation: number
ffmc?: number
isi?: number
fwi?: number
}

export interface HourlyReadings {
station: Station
values: HourlyReading[]
}

export interface HourliesResponse {
hourlies: HourlyReadings[]
}

export async function getHourlies(stationCodes: number[]): Promise<HourlyReadings[]> {
const url = '/hourlies/'

try {
const { data } = await axios.post<HourliesResponse>(url, {
stations: stationCodes
})
return data.hourlies
} catch (err) {
throw err.toString()
}
}
5 changes: 4 additions & 1 deletion src/app/rootReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import stationsReducer from 'features/stations/slices/stationsSlice'
import percentilesReducer from 'features/percentileCalculator/slices/percentilesSlice'
import authReducer from 'features/auth/slices/authenticationSlice'
import forecastsReducer from 'features/dailyForecasts/slices/ForecastsSlice'
import hourliesReducer from 'features/hourlies/slices/HourliesSlice'

const rootReducer = combineReducers({
stations: stationsReducer,
percentiles: percentilesReducer,
authentication: authReducer,
forecasts: forecastsReducer
forecasts: forecastsReducer,
hourlies: hourliesReducer
})

// Infer whatever gets returned from rootReducer and use it as the type of the root state
Expand All @@ -22,3 +24,4 @@ export const selectPercentiles = (state: RootState) => state.percentiles
export const selectAuthentication = (state: RootState) => state.authentication
export const selectToken = (state: RootState) => state.authentication.token
export const selectForecasts = (state: RootState) => state.forecasts
export const selectHourlies = (state: RootState) => state.hourlies
23 changes: 23 additions & 0 deletions src/features/dailyForecasts/DailyForecastsPage.mock.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ForecastsResponse } from 'api/forecastAPI'
import { HourliesResponse } from 'api/hourliesAPI'
import { RecursivePartial } from 'types/utilTypes'

export const mockStations = [
Expand All @@ -23,3 +24,25 @@ export const mockForecastsResponse: RecursivePartial<ForecastsResponse> = {
}
]
}

export const mockHourliesResponse: RecursivePartial<HourliesResponse> = {
hourlies: [
{
station: mockStations[0],
values: [
{
datetime: '2020-05-15T11:00:00',
temperature: 16.9,
relative_humidity: 37.0,
wind_speed: 9.0,
wind_direction: 45.0,
barometric_pressure: 0.0,
precipitation: 0.0,
ffmc: undefined,
isi: undefined,
fwi: undefined
}
]
}
]
}
24 changes: 16 additions & 8 deletions src/features/dailyForecasts/DailyForecastsPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { renderWithRedux } from 'utils/testUtils'
import { DailyForecastsPage } from 'features/dailyForecasts/DailyForecastsPage'
import {
mockStations,
mockForecastsResponse
mockForecastsResponse,
mockHourliesResponse
} from 'features/dailyForecasts/DailyForecastsPage.mock'

const mockAxios = new MockAdapter(axios)
Expand Down Expand Up @@ -49,9 +50,10 @@ it('renders weather stations dropdown with data', async () => {
expect(selectStations(store.getState()).stations).toEqual(mockStations)
})

it('renders daily forecast values in response to user inputs', async () => {
it('renders daily forecast and hourly values in response to user inputs', async () => {
mockAxios.onGet('/stations/').replyOnce(200, { weather_stations: mockStations })
mockAxios.onPost('/forecasts/').replyOnce(200, mockForecastsResponse)
mockAxios.onPost('/hourlies/').replyOnce(200, mockHourliesResponse)

const { getByText, getByTestId } = renderWithRedux(<DailyForecastsPage />)

Expand All @@ -70,12 +72,18 @@ it('renders daily forecast values in response to user inputs', async () => {

// Wait until the forecasts are fetched
await waitForElement(() => getByTestId('daily-forecast-displays'))
// Wait until the hourlies are fetched
await waitForElement(() => getByTestId('hourly-readings-displays'))

// Validate the correct request body
expect(mockAxios.history.post.length).toBe(1)
expect(mockAxios.history.post[0].data).toBe(
JSON.stringify({
stations: [1]
})
)
// There should have been two requests, one for forecasts and one for hourlies.
expect(mockAxios.history.post.length).toBe(2)
// Each of those request should ask for a station
mockAxios.history.post.forEach(post => {
expect(post.data).toBe(
JSON.stringify({
stations: [1]
})
)
})
})
12 changes: 9 additions & 3 deletions src/features/dailyForecasts/DailyForecastsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useDispatch, useSelector } from 'react-redux'
import { makeStyles } from '@material-ui/core/styles'

import { Station } from 'api/stationAPI'
import { selectAuthentication, selectForecasts } from 'app/rootReducer'
import { selectAuthentication, selectForecasts, selectHourlies } from 'app/rootReducer'
import { PageHeader, PageTitle, Container, Button } from 'components'
import {
authenticate,
Expand All @@ -12,7 +12,9 @@ import {
import { fetchWxStations } from 'features/stations/slices/stationsSlice'
import { WxStationDropdown } from 'features/stations/components/WxStationDropdown'
import { fetchForecasts } from 'features/dailyForecasts/slices/ForecastsSlice'
import { fetchHistoricalReadings } from 'features/hourlies/slices/HourliesSlice'
import { DailyForecastsDisplay } from 'features/dailyForecasts/components/DailyForecastsDisplay'
import { HourlyReadingsDisplay } from 'features/hourlies/components/HourlyReadingsDisplay'

const useStyles = makeStyles({
stationDropdown: {
Expand All @@ -28,7 +30,9 @@ export const DailyForecastsPage = () => {
const [selectedStations, setStations] = useState<Station[]>([])

const { isAuthenticated, authenticating, error } = useSelector(selectAuthentication)
const { loading: wxDataLoading } = useSelector(selectForecasts)
const { loading: forecastDataLoading } = useSelector(selectForecasts)
const { loading: hourlyDataLoading } = useSelector(selectHourlies)
const wxDataLoading = forecastDataLoading || hourlyDataLoading

useEffect(() => {
dispatch(authenticate())
Expand All @@ -42,6 +46,7 @@ export const DailyForecastsPage = () => {
const onSubmitClick = () => {
const stationCodes = selectedStations.map(s => s.code)
dispatch(fetchForecasts(stationCodes))
dispatch(fetchHistoricalReadings(stationCodes))
}

if (error) {
Expand Down Expand Up @@ -76,9 +81,10 @@ export const DailyForecastsPage = () => {
variant="contained"
color="primary"
>
Get Wx Forecast Data
Get Historic Readings and Forecasted Global Model Data
</Button>
<DailyForecastsDisplay />
<HourlyReadingsDisplay />
</Container>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export const DailyForecastsDisplay = () => {
{forecasts.map(({ station, values }) => (
<Paper className={classes.forecast} key={station.code}>
<Typography className={classes.station} variant="subtitle1" component="div">
Weather Station: {`${station.name} (${station.code})`}
Model noon values for weather station: {`${station.name} (${station.code})`}
</Typography>
<Typography
className={classes.units}
Expand Down Expand Up @@ -90,7 +90,7 @@ export const DailyForecastsDisplay = () => {
<TableCell align="left">RH</TableCell>
{values.map(v => (
<TableCell key={v.datetime} align="left">
{v.relative_humidity.toFixed(FORECAST_VALUES_DECIMAL)}
{Math.round(v.relative_humidity)}
</TableCell>
))}
</TableRow>
Expand Down
140 changes: 140 additions & 0 deletions src/features/hourlies/components/HourlyReadingsDisplay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import React from 'react'
import { useSelector } from 'react-redux'
import { makeStyles } from '@material-ui/core/styles'

import Table from '@material-ui/core/Table'
import TableBody from '@material-ui/core/TableBody'
import TableCell from '@material-ui/core/TableCell'
import TableContainer from '@material-ui/core/TableContainer'
import TableRow from '@material-ui/core/TableRow'
import Paper from '@material-ui/core/Paper'
import Typography from '@material-ui/core/Typography'
import moment from 'moment'

import { selectHourlies } from 'app/rootReducer'
import { ErrorMessage } from 'components/ErrorMessage'
import { HOURLY_VALUES_DECIMAL, PDT_UTC_OFFSET } from 'utils/constants'

const useStyles = makeStyles({
table: {
minWidth: 650
},
station: {
paddingTop: 8,
paddingLeft: 16
},
hourlies: {
marginBottom: 20
}
})

export const HourlyReadingsDisplay = () => {
const classes = useStyles()
const { error, hourlies } = useSelector(selectHourlies)

if (error) {
return (
<ErrorMessage
error={error}
context="while fetching hourly readings"
marginTop={5}
/>
)
}

if (hourlies.length === 0) {
return null
}

return (
<div data-testid="hourly-readings-displays">
{hourlies.map(({ station, values }) => (
<Paper className={classes.hourlies} key={station.code}>
<Typography className={classes.station} variant="subtitle1" component="div">
Hourly readings for weather station: {`${station.name} (${station.code})`}
</Typography>
<TableContainer>
<Table className={classes.table} size="small" aria-label="weather data table">
<TableBody>
<TableRow>
<TableCell align="left">Date (PDT)</TableCell>
{values.map(v => (
<TableCell key={v.datetime} align="left">
{moment(v.datetime)
.utcOffset(PDT_UTC_OFFSET)
.format('YYYY-MM-DD HH:mm')}
</TableCell>
))}
</TableRow>
<TableRow>
<TableCell>Temperature</TableCell>
{values.map(v => (
<TableCell key={v.datetime} align="left">
{v.temperature.toFixed(HOURLY_VALUES_DECIMAL)}
</TableCell>
))}
</TableRow>
<TableRow>
<TableCell>Relative Humidity</TableCell>
{values.map(v => (
<TableCell key={v.datetime} align="left">
{Math.round(v.relative_humidity)}
</TableCell>
))}
</TableRow>
<TableRow>
<TableCell>Wind Speed</TableCell>
{values.map(v => (
<TableCell key={v.datetime} align="left">
{v.wind_speed.toFixed(HOURLY_VALUES_DECIMAL)}
</TableCell>
))}
</TableRow>
<TableRow>
<TableCell>Wind Direction</TableCell>
{values.map(v => (
<TableCell key={v.datetime} align="left">
{Math.round(v.wind_direction)}
</TableCell>
))}
</TableRow>
<TableRow>
<TableCell>Precipitation</TableCell>
{values.map(v => (
<TableCell key={v.datetime} align="left">
{v.precipitation.toFixed(HOURLY_VALUES_DECIMAL)}
</TableCell>
))}
</TableRow>
<TableRow>
<TableCell>FFMC</TableCell>
{values.map(v => (
<TableCell key={v.datetime} align="left">
{v.ffmc?.toFixed(HOURLY_VALUES_DECIMAL)}
</TableCell>
))}
</TableRow>
<TableRow>
<TableCell>ISI</TableCell>
{values.map(v => (
<TableCell key={v.datetime} align="left">
{v.isi?.toFixed(HOURLY_VALUES_DECIMAL)}
</TableCell>
))}
</TableRow>
<TableRow>
<TableCell>FWI</TableCell>
{values.map(v => (
<TableCell key={v.datetime} align="left">
{v.fwi?.toFixed(HOURLY_VALUES_DECIMAL)}
</TableCell>
))}
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Paper>
))}
</div>
)
}
Loading

0 comments on commit e0a4dbf

Please sign in to comment.