diff --git a/CHANGELOG.md b/CHANGELOG.md index a3679e482cb9a38c521822eca5d858c6cc71213d..867a64b50768aa0b0b1c382959b8bf9c65fba77f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased](https://github.com/usgs/waterdataui/compare/waterdataui-0.42.0...master) +### Added +- A column indicating the approval status of the data shows in the DV Data Table. + ### Fixed - The converted Fahrenheit line no longer drops off graph with zero values. +- Locations with only some flood level indicators no longer show NaN + ## [0.42.0](https://github.com/usgs/waterdataui/compare/waterdataui-0.40.0...waterdataui-0.42.0) ### Added - Added display of discrete ground water level data on the IV hydrograph. diff --git a/Makefile b/Makefile index 87f8373ba969525b83c2c7f3c10eaa57e23c5bab..ba599d3a5f674eeea75a19f4d66c8f20f780943b 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ MAKEPID:= $(shell echo $$PPID) env: env-assets env-wdfn # removing testing of assets until refactor is complete. test: test-assets test-wdfn -test: test-wdfn +test: test-assets test-wdfn clean: clean-assets clean-graph-server clean-wdfn cleanenv: cleanenv-assets cleanenv-wdfn build: env build-assets build-wdfn diff --git a/assets/src/scripts/config.js b/assets/src/scripts/config.js index e9fec1bcca7e0bf9f3a653908aa81c8d661c97ea..322d32030806d4c6a205aef78e1653411d388e9b 100644 --- a/assets/src/scripts/config.js +++ b/assets/src/scripts/config.js @@ -40,12 +40,6 @@ export default { ] }, - // Below is a listing of the known temperature codes and counterparts - // '00020': '00021' - air temperature C:F - // '00010': '00011' - water temperature C:F - // '45589': '45590' - Temperature, internal, within equipment shelter C:F - CELSIUS_CODES_WITH_FAHRENHEIT_COUNTERPARTS: ['00020', '00010', '45589'], - WATER_ALERT_PARAMETER_CODES: [ '00060', '00055', diff --git a/assets/src/scripts/mock-service-data.js b/assets/src/scripts/mock-service-data.js index d6ca4ab49e3c7aa1b8c6e86bcfa3de90879118b9..c1f132d690e32854fcb80368f128bb4c93839796 100644 --- a/assets/src/scripts/mock-service-data.js +++ b/assets/src/scripts/mock-service-data.js @@ -3235,37 +3235,37 @@ export const MOCK_GWLEVEL_DATA = "value": [ { "value": "26.07", - "qualifiers": [], + "qualifiers": ["A", "1"], "dateTime": "2020-01-23T09:06:00.000" }, { "value": "27.27", - "qualifiers": [], + "qualifiers": ["A", "1"], "dateTime": "2020-03-19T15:11:00.000" }, { "value": "27.33", - "qualifiers": [], + "qualifiers": ["A", "1"], "dateTime": "2020-05-13T11:47:00.000" }, { "value": "29.19", - "qualifiers": [], + "qualifiers": ["A", "1"], "dateTime": "2020-07-23T11:45:00.000" }, { "value": "25.55", - "qualifiers": [], + "qualifiers": ["A", "1"], "dateTime": "2020-08-24T16:04:00.000" }, { "value": "25.03", - "qualifiers": [], + "qualifiers": ["A", "1"], "dateTime": "2020-10-01T18:10:00.000" }, { "value": "24.54", - "qualifiers": [], + "qualifiers": ["A", "1"], "dateTime": "2020-11-17T12:57:00.000" } ], @@ -3291,6 +3291,149 @@ export const MOCK_GWLEVEL_DATA = "typeSubstituted": false }`; +export const MOCK_TEMP_C_IV_DATA = +`{ + "name": "ns1:timeSeriesResponseType", + "declaredType": "org.cuahsi.waterml.TimeSeriesResponseType", + "scope": "javax.xml.bind.JAXBElement$GlobalScope", + "value": { + "queryInfo": { + "queryURL": "http://waterservices.usgs.gov/nwis/iv/sites=05370000¶meterCd=00010&period=P1D&siteStatus=all&format=json", + "criteria": { + "locationParam": "[ALL:05370000]", + "variableParam": "[00010]", + "parameter": [] + }, + "note": [{ + "value": "[ALL:05370000]", + "title": "filter:sites" + }, { + "value": "[mode=PERIOD, period=P1D, modifiedSince=null]", + "title": "filter:timeRange" + }, { + "value": "methodIds=[ALL]", + "title": "filter:methodId" + }, { + "value": "2021-02-22T15:00:05.479Z", + "title": "requestDT" + }, { + "value": "a5b93860-751e-11eb-af07-2cea7f58f5ca", + "title": "requestId" + }, { + "value": "Provisional data are subject to revision. Go to http://waterdata.usgs.gov/nwis/help/?provisional for more information.", + "title": "disclaimer" + }, { + "value": "vaas01", + "title": "server" + }] + }, + "timeSeries": [{ + "sourceInfo": { + "siteName": "EAU GALLE RIVER AT SPRING VALLEY, WI", + "siteCode": [{ + "value": "05370000", + "network": "NWIS", + "agencyCode": "USGS" + }], + "timeZoneInfo": { + "defaultTimeZone": { + "zoneOffset": "-06:00", + "zoneAbbreviation": "CST" + }, + "daylightSavingsTimeZone": { + "zoneOffset": "-05:00", + "zoneAbbreviation": "CDT" + }, + "siteUsesDaylightSavingsTime": true + }, + "geoLocation": { + "geogLocation": { + "srs": "EPSG:4326", + "latitude": 44.85277778, + "longitude": -92.2383333 + }, + "localSiteXY": [] + }, + "note": [], + "siteType": [], + "siteProperty": [{ + "value": "ST", + "name": "siteTypeCd" + }, { + "value": "07050005", + "name": "hucCd" + }, { + "value": "55", + "name": "stateCd" + }, { + "value": "55093", + "name": "countyCd" + }] + }, + "variable": { + "variableCode": [{ + "value": "00010", + "network": "NWIS", + "vocabulary": "NWIS:UnitValues", + "variableID": 45807042, + "default": true + }], + "variableName": "Temperature, water, °C", + "variableDescription": "Temperature, water, degrees Celsius", + "valueType": "Derived Value", + "unit": { + "unitCode": "deg C" + }, + "options": { + "option": [{ + "name": "Statistic", + "optionCode": "00000" + }] + }, + "note": [], + "noDataValue": -999999.0, + "variableProperty": [], + "oid": "45807042" + }, + "values": [{ + "value": [{ + "value": "2.0", + "qualifiers": ["P"], + "dateTime": "2021-02-21T09:15:00.000-06:00" + }, { + "value": "0.0", + "qualifiers": ["P"], + "dateTime": "2021-02-21T09:30:00.000-06:00" + }, { + "value": "2.1", + "qualifiers": ["P"], + "dateTime": "2021-02-21T09:45:00.000-06:00" + }], + "qualifier": [{ + "qualifierCode": "P", + "qualifierDescription": "Provisional data subject to revision.", + "qualifierID": 0, + "network": "NWIS", + "vocabulary": "uv_rmk_cd" + }], + "qualityControlLevel": [], + "method": [{ + "methodDescription": "", + "methodID": 157775 + }], + "source": [], + "offset": [], + "sample": [], + "censorCode": [] + }], + "name": "USGS:05370000:00010:00000" + }] + }, + "nil": false, + "globalScope": true, + "typeSubstituted": false +}`; + export const MOCK_STATISTICS_JSON = { '00010': { '153885': [{ @@ -3452,3 +3595,588 @@ export const MOCK_STATISTICS_JSON = { }] } }; + +export const MOCK_LATEST_IV_DATA = ` +{ + "name": "ns1:timeSeriesResponseType", + "declaredType": "org.cuahsi.waterml.TimeSeriesResponseType", + "scope": "javax.xml.bind.JAXBElement$GlobalScope", + "value": { + "queryInfo": { + "queryURL": "http://waterservices.usgs.gov/nwis/iv/sites=354133082042203&&siteStatus=all&format=json", + "criteria": { + "locationParam": "[ALL:354133082042203]", + "variableParam": "ALL", + "parameter": [] + }, + "note": [{ + "value": "[ALL:354133082042203]", + "title": "filter:sites" + }, { + "value": "[mode=LATEST, modifiedSince=null]", + "title": "filter:timeRange" + }, { + "value": "methodIds=[ALL]", + "title": "filter:methodId" + }, { + "value": "2021-02-22T16:05:18.032Z", + "title": "requestDT" + }, { + "value": "c1c96df0-7527-11eb-93f2-2cea7f5e5ede", + "title": "requestId" + }, { + "value": "Provisional data are subject to revision. Go to http://waterdata.usgs.gov/nwis/help/?provisional for more information.", + "title": "disclaimer" + }, { + "value": "sdas01", + "title": "server" + }] + }, + "timeSeries": [{ + "sourceInfo": { + "siteName": "MC-109 NEAR PLEASANT GARDENS, NC (BEDROCK)", + "siteCode": [{ + "value": "354133082042203", + "network": "NWIS", + "agencyCode": "USGS" + }], + "timeZoneInfo": { + "defaultTimeZone": { + "zoneOffset": "-05:00", + "zoneAbbreviation": "EST" + }, + "daylightSavingsTimeZone": { + "zoneOffset": "-04:00", + "zoneAbbreviation": "EDT" + }, + "siteUsesDaylightSavingsTime": true + }, + "geoLocation": { + "geogLocation": { + "srs": "EPSG:4326", + "latitude": 35.6926111, + "longitude": -82.0729444 + }, + "localSiteXY": [] + }, + "note": [], + "siteType": [], + "siteProperty": [{ + "value": "GW", + "name": "siteTypeCd" + }, { + "value": "03050101", + "name": "hucCd" + }, { + "value": "37", + "name": "stateCd" + }, { + "value": "37111", + "name": "countyCd" + }] + }, + "variable": { + "variableCode": [{ + "value": "72019", + "network": "NWIS", + "vocabulary": "NWIS:UnitValues", + "variableID": 52331280, + "default": true + }], + "variableName": "Depth to water level, ft below land surface", + "variableDescription": "Depth to water level, feet below land surface", + "valueType": "Derived Value", + "unit": { + "unitCode": "ft" + }, + "options": { + "option": [{ + "name": "Statistic", + "optionCode": "00000" + }] + }, + "note": [], + "noDataValue": -999999.0, + "variableProperty": [], + "oid": "52331280" + }, + "values": [{ + "value": [{ + "value": "26.14", + "qualifiers": ["P"], + "dateTime": "2021-02-22T10:45:00.000-05:00" + }], + "qualifier": [{ + "qualifierCode": "P", + "qualifierDescription": "Provisional data subject to revision.", + "qualifierID": 0, + "network": "NWIS", + "vocabulary": "uv_rmk_cd" + }], + "qualityControlLevel": [], + "method": [{ + "methodDescription": "", + "methodID": 90649 + }], + "source": [], + "offset": [], + "sample": [], + "censorCode": [] + }], + "name": "USGS:354133082042203:72019:00000" + }, { + "sourceInfo": { + "siteName": "EAU GALLE RIVER AT SPRING VALLEY, WI", + "siteCode": [{ + "value": "05370000", + "network": "NWIS", + "agencyCode": "USGS" + }], + "timeZoneInfo": { + "defaultTimeZone": { + "zoneOffset": "-06:00", + "zoneAbbreviation": "CST" + }, + "daylightSavingsTimeZone": { + "zoneOffset": "-05:00", + "zoneAbbreviation": "CDT" + }, + "siteUsesDaylightSavingsTime": true + }, + "geoLocation": { + "geogLocation": { + "srs": "EPSG:4326", + "latitude": 44.85277778, + "longitude": -92.2383333 + }, + "localSiteXY": [] + }, + "note": [], + "siteType": [], + "siteProperty": [{ + "value": "ST", + "name": "siteTypeCd" + }, { + "value": "07050005", + "name": "hucCd" + }, { + "value": "55", + "name": "stateCd" + }, { + "value": "55093", + "name": "countyCd" + }] + }, + "variable": { + "variableCode": [{ + "value": "00010", + "network": "NWIS", + "vocabulary": "NWIS:UnitValues", + "variableID": 45807042, + "default": true + }], + "variableName": "Temperature, water, °C", + "variableDescription": "Temperature, water, degrees Celsius", + "valueType": "Derived Value", + "unit": { + "unitCode": "deg C" + }, + "options": { + "option": [{ + "name": "Statistic", + "optionCode": "00000" + }] + }, + "note": [], + "noDataValue": -999999.0, + "variableProperty": [], + "oid": "45807042" + }, + "values": [{ + "value": [{ + "value": "2.3", + "qualifiers": ["P"], + "dateTime": "2021-02-22T10:00:00.000-06:00" + }], + "qualifier": [{ + "qualifierCode": "P", + "qualifierDescription": "Provisional data subject to revision.", + "qualifierID": 0, + "network": "NWIS", + "vocabulary": "uv_rmk_cd" + }], + "qualityControlLevel": [], + "method": [{ + "methodDescription": "", + "methodID": 157775 + }], + "source": [], + "offset": [], + "sample": [], + "censorCode": [] + }], + "name": "USGS:05370000:00010:00000" + }] + }, + "nil": false, + "globalScope": true, + "typeSubstituted": false +}`; + +export const MOCK_LATEST_GW_DATA = ` +{ + "name": "ns1:timeSeriesResponseType", + "declaredType": "org.cuahsi.waterml.TimeSeriesResponseType", + "scope": "javax.xml.bind.JAXBElement$GlobalScope", + "value": { + "queryInfo": { + "queryURL": "http://waterservices.usgs.gov/nwis/gwlevels/sites=354133082042203&format=json", + "criteria": { + "locationParam": "[ALL:354133082042203]", + "variableParam": "ALL", + "parameter": [] + }, + "note": [{ + "value": "[ALL:354133082042203]", + "title": "filter:sites" + }, { + "value": "[mode=LATEST, modifiedSince=null]", + "title": "filter:timeRange" + }, { + "value": "methodIds=[ALL]", + "title": "filter:methodId" + }, { + "value": "2021-02-22T16:05:17.901Z", + "title": "requestDT" + }, { + "value": "c1b570c0-7527-11eb-9c02-2cea7f5e5ede", + "title": "requestId" + }, { + "value": "Provisional data are subject to revision. Go to http://waterdata.usgs.gov/nwis/help/?provisional for more information.", + "title": "disclaimer" + }, { + "value": "sdas01", + "title": "server" + }] + }, + "timeSeries": [{ + "sourceInfo": { + "siteName": "MC-109 NEAR PLEASANT GARDENS, NC (BEDROCK)", + "siteCode": [{ + "value": "354133082042203", + "network": "NWIS", + "agencyCode": "USGS" + }], + "timeZoneInfo": { + "defaultTimeZone": { + "zoneOffset": "-05:00", + "zoneAbbreviation": "EST" + }, + "daylightSavingsTimeZone": { + "zoneOffset": "-04:00", + "zoneAbbreviation": "EDT" + }, + "siteUsesDaylightSavingsTime": true + }, + "geoLocation": { + "geogLocation": { + "srs": "EPSG:4326", + "latitude": 35.6926111, + "longitude": -82.0729444 + }, + "localSiteXY": [] + }, + "note": [], + "siteType": [], + "siteProperty": [{ + "value": "GW", + "name": "siteTypeCd" + }, { + "value": "03050101", + "name": "hucCd" + }, { + "value": "37", + "name": "stateCd" + }, { + "value": "37111", + "name": "countyCd" + }] + }, + "variable": { + "variableCode": [{ + "value": "62610", + "network": "NWIS", + "vocabulary": "NWIS:UnitValues", + "variableID": 51413516, + "default": true + }], + "variableName": "Groundwater level above NGVD 1929, feet", + "variableDescription": "Groundwater level above NGVD 1929, feet", + "valueType": "Derived Value", + "unit": { + "unitCode": "ft" + }, + "options": { + "option": [{ + "name": "Statistic", + "optionCode": "00000" + }] + }, + "note": [], + "noDataValue": -999999.0, + "variableProperty": [], + "oid": "51413516" + }, + "values": [{ + "value": [{ + "value": "27.49", + "qualifiers": ["P", "1"], + "dateTime": "2021-01-21T18:54:00.000" + }, { + "value": "1317.11", + "qualifiers": ["P", "1"], + "dateTime": "2021-01-21T18:54:00.000" + }, { + "value": "1317.40", + "qualifiers": ["P", "1"], + "dateTime": "2021-01-21T18:54:00.000" + }], + "qualifier": [{ + "qualifierCode": "1", + "qualifierDescription": "Static", + "qualifierID": 0, + "network": "NWIS", + "vocabulary": "uv_rmk_cd" + }, { + "qualifierCode": "P", + "qualifierDescription": "Provisional data subject to revision.", + "qualifierID": 1, + "network": "NWIS", + "vocabulary": "uv_rmk_cd" + }], + "qualityControlLevel": [], + "method": [{ + "methodID": 1 + }], + "source": [], + "offset": [], + "sample": [], + "censorCode": [] + }], + "name": "USGS:354133082042203:62610:00000" + }, { + "sourceInfo": { + "siteName": "MC-109 NEAR PLEASANT GARDENS, NC (BEDROCK)", + "siteCode": [{ + "value": "354133082042203", + "network": "NWIS", + "agencyCode": "USGS" + }], + "timeZoneInfo": { + "defaultTimeZone": { + "zoneOffset": "-05:00", + "zoneAbbreviation": "EST" + }, + "daylightSavingsTimeZone": { + "zoneOffset": "-04:00", + "zoneAbbreviation": "EDT" + }, + "siteUsesDaylightSavingsTime": true + }, + "geoLocation": { + "geogLocation": { + "srs": "EPSG:4326", + "latitude": 35.6926111, + "longitude": -82.0729444 + }, + "localSiteXY": [] + }, + "note": [], + "siteType": [], + "siteProperty": [{ + "value": "GW", + "name": "siteTypeCd" + }, { + "value": "03050101", + "name": "hucCd" + }, { + "value": "37", + "name": "stateCd" + }, { + "value": "37111", + "name": "countyCd" + }] + }, + "variable": { + "variableCode": [{ + "value": "62611", + "network": "NWIS", + "vocabulary": "NWIS:UnitValues", + "variableID": 51413517, + "default": true + }], + "variableName": "Groundwater level above NAVD 1988, ft", + "variableDescription": "Groundwater level above NAVD 1988, feet", + "valueType": "Derived Value", + "unit": { + "unitCode": "ft" + }, + "options": { + "option": [{ + "name": "Statistic", + "optionCode": "00000" + }] + }, + "note": [], + "noDataValue": -999999.0, + "variableProperty": [], + "oid": "51413517" + }, + "values": [{ + "value": [{ + "value": "27.49", + "qualifiers": ["P", "1"], + "dateTime": "2021-01-21T18:54:00.000" + }, { + "value": "1317.11", + "qualifiers": ["P", "1"], + "dateTime": "2021-01-21T18:54:00.000" + }, { + "value": "1317.40", + "qualifiers": ["P", "1"], + "dateTime": "2021-01-21T18:54:00.000" + }], + "qualifier": [{ + "qualifierCode": "1", + "qualifierDescription": "Static", + "qualifierID": 0, + "network": "NWIS", + "vocabulary": "uv_rmk_cd" + }, { + "qualifierCode": "P", + "qualifierDescription": "Provisional data subject to revision.", + "qualifierID": 1, + "network": "NWIS", + "vocabulary": "uv_rmk_cd" + }], + "qualityControlLevel": [], + "method": [{ + "methodID": 2 + }], + "source": [], + "offset": [], + "sample": [], + "censorCode": [] + }], + "name": "USGS:354133082042203:62611:00000" + }, { + "sourceInfo": { + "siteName": "MC-109 NEAR PLEASANT GARDENS, NC (BEDROCK)", + "siteCode": [{ + "value": "354133082042203", + "network": "NWIS", + "agencyCode": "USGS" + }], + "timeZoneInfo": { + "defaultTimeZone": { + "zoneOffset": "-05:00", + "zoneAbbreviation": "EST" + }, + "daylightSavingsTimeZone": { + "zoneOffset": "-04:00", + "zoneAbbreviation": "EDT" + }, + "siteUsesDaylightSavingsTime": true + }, + "geoLocation": { + "geogLocation": { + "srs": "EPSG:4326", + "latitude": 35.6926111, + "longitude": -82.0729444 + }, + "localSiteXY": [] + }, + "note": [], + "siteType": [], + "siteProperty": [{ + "value": "GW", + "name": "siteTypeCd" + }, { + "value": "03050101", + "name": "hucCd" + }, { + "value": "37", + "name": "stateCd" + }, { + "value": "37111", + "name": "countyCd" + }] + }, + "variable": { + "variableCode": [{ + "value": "72019", + "network": "NWIS", + "vocabulary": "NWIS:UnitValues", + "variableID": 52331280, + "default": true + }], + "variableName": "Depth to water level, ft below land surface", + "variableDescription": "Depth to water level, feet below land surface", + "valueType": "Derived Value", + "unit": { + "unitCode": "ft" + }, + "options": { + "option": [{ + "name": "Statistic", + "optionCode": "00000" + }] + }, + "note": [], + "noDataValue": -999999.0, + "variableProperty": [], + "oid": "52331280" + }, + "values": [{ + "value": [{ + "value": "27.49", + "qualifiers": ["P", "1"], + "dateTime": "2021-01-21T18:54:00.000" + }, { + "value": "1317.11", + "qualifiers": ["P", "1"], + "dateTime": "2021-01-21T18:54:00.000" + }, { + "value": "1317.40", + "qualifiers": ["P", "1"], + "dateTime": "2021-01-21T18:54:00.000" + }], + "qualifier": [{ + "qualifierCode": "1", + "qualifierDescription": "Static", + "qualifierID": 0, + "network": "NWIS", + "vocabulary": "uv_rmk_cd" + }, { + "qualifierCode": "P", + "qualifierDescription": "Provisional data subject to revision.", + "qualifierID": 1, + "network": "NWIS", + "vocabulary": "uv_rmk_cd" + }], + "qualityControlLevel": [], + "method": [{ + "methodID": 3 + }], + "source": [], + "offset": [], + "sample": [], + "censorCode": [] + }], + "name": "USGS:354133082042203:72019:00000" + }] + }, + "nil": false, + "globalScope": true, + "typeSubstituted": false +}`; \ No newline at end of file diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/data-table.js b/assets/src/scripts/monitoring-location/components/hydrograph/data-table.js index 3d7c07c5c015331ec9c29e50130edb914855a9e9..19b803e06b0ea52bd83af7df9982b40e3da36656 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/data-table.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/data-table.js @@ -14,12 +14,12 @@ const TABLE_CAPTION = { const COLUMN_HEADINGS = { iv: ['Parameter', 'Time', 'Result', 'Approval', 'Masks'], - gw: ['Parameter', 'Time', 'Result'] + gw: ['Parameter', 'Time', 'Result', 'Approval'] }; const VALUE_NAMES = { iv: ['parameterName', 'dateTime', 'result', 'approvals', 'masks'], - gw: ['parameterName', 'dateTime', 'result'] + gw: ['parameterName', 'dateTime', 'result', 'approvals'] }; const CONTAINER_ID = { diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/data-table.test.js b/assets/src/scripts/monitoring-location/components/hydrograph/data-table.test.js index 65bb01e1709c3c0815584b619e9eda999b2dcede..3ea84efd256021b033ab6c6f611a69e92f6ea3d2 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/data-table.test.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/data-table.test.js @@ -3,150 +3,12 @@ import {select} from 'd3-selection'; import {configureStore} from 'ml/store'; import {drawDataTables} from './data-table'; +import {TEST_PRIMARY_IV_DATA, TEST_GW_LEVELS} from './mock-hydrograph-state'; -const TEST_DATA = { - ivTimeSeriesData: { - queryInfo: { - 'current:custom:72019': { - 'queryURL': 'http://waterservices.usgs.gov/nwis/iv/sites=05370000&period=P7D&siteStatus=all&format=json', - notes: { - 'filter:timeRange': { - mode: 'RANGE', - interval: {start: 1520351200000, end: 1520353700000} - } - } - } - }, - methods: { - 69928: { - methodDescription: '', - methodID: 69928 - }, - 69929: { - methodDescription: '', - methodID: 69929 - } - }, - timeSeries: { - '69928:00060': { - tsKey: 'current:P7D', - startTime: new Date('2018-03-06T15:45:00.000Z'), - endTime: new Date('2018-03-13t13:45:00.000Z'), - variable: '45807197', - method: 69928, - points: [{ - value: 10, - qualifiers: ['P'], - dateTime: 1520351100000 - }, { - value: null, - qualifiers: ['P', 'ICE'], - dateTime: 1520352000000 - }, { - value: null, - qualifiers: ['P', 'FLD'], - dateTime: 1520352900000 - }] - }, - '69929:00010': { - tsKey: 'compare:P7D', - variable: '45807196', - method: 69929, - points: [{ - value: 1, - qualifiers: ['P'], - dateTime: 1488815100000 - }, { - value: 2, - qualifiers: ['P'], - dateTime: 1488816000000 - }, { - value: 3, - qualifiers: ['P'], - dateTime: 1488816900000 - }] - }, - '69930:00045': { - tsKey: 'current:P7D', - - variable: '45807140', - method: 69930, - points: [{ - value: 0, - qualifiers: ['P'], - dateTime: 1520351100000 - }, { - value: 0.01, - qualifiers: ['P'], - dateTime: 1520352000000 - }, { - value: 0.02, - qualifiers: ['P'], - dateTime: 1520352900000 - }, { - value: 0.03, - qualifiers: ['P'], - dateTime: 1520353800000 - }] - } - }, - variables: { - '45807197': { - variableCode: {value: '00060'}, - variableName: 'Streamflow', - variableDescription: 'Discharge, cubic feet per second', - oid: '45807197' - }, - '45807196': { - variableCode: {value: '00010'}, - variableName: 'Gage Height', - variableDescription: 'Gage Height in feet', - oid: '45807196' - }, - '45807140': { - variableCode: {value: '00045'}, - variableName: 'Precipitation', - variableDescription: 'Precipitation in inches' - }, - '45807141' : { - variableCode: {value: '72019'}, - variableName: 'Depth to water level', - variableDescription: 'Dept to water level in feet' - } - } - }, - ivTimeSeriesState: { - currentIVVariableID: '45807197', - currentIVDateRange: 'P7D', - currentIVMethodID: 69928 - }, - discreteData: { - groundwaterLevels: { - '45807141': { - variable: { - variableCode: {value: '72019'}, - variableName: 'Depth to water level', - variableDescription: 'Dept to water level in feet' - }, - values: [{ - value: '0', - qualifiers: [], - dateTime: 1520351100000 - }, { - value: '0.01', - qualifiers: [], - dateTime: 1520352000000 - }, { - value: '0.02', - qualifiers: [], - dateTime: 1520352900000 - }, { - value: '0.03', - qualifiers: [], - dateTime: 1520353800000 - }] - } - } +const TEST_STATE = { + hydrographData: { + primaryIVData: TEST_PRIMARY_IV_DATA, + groundwaterLevels: TEST_GW_LEVELS } }; @@ -163,32 +25,48 @@ describe('monitoring-location/components/hydrograph/data-table', () => { }); it('table with expected data', () => { - store = configureStore(TEST_DATA); - + store = configureStore({ + hydrographData: { + primaryIVData: TEST_PRIMARY_IV_DATA, + groundwaterLevels: TEST_GW_LEVELS + }, + hydrographState: { + selectedIVMethodID: '90649' + } + }); drawDataTables(testDiv, store); - expect(testDiv.selectAll('table').size()).toBe(1); - expect(testDiv.select('#iv-table-container').select('caption').text()).toEqual('Instantaneous value data'); - expect(testDiv.select('tbody').selectAll('tr').size()).toBe(3); + + const ivTable = testDiv.select('#iv-table-container').select('table'); + expect(ivTable.select('caption').text()).toBe('Instantaneous value data'); + expect(ivTable.selectAll('tr').size()).toBe(10); + const gwTable = testDiv.select('#gw-table-container').select('table'); + expect(gwTable.select('caption').text()).toBe('Field visit data'); + expect(gwTable.selectAll('tr').size()).toBe(5); }); - it('Expect that changing the variable changes the expected data table', () => { - const testData = { - ...TEST_DATA, - ivTimeSeriesState: { - ...TEST_DATA.ivTimeSeriesState, - currentIVVariableID: '45807141', - currentIVDateRange: 'custom', - customIVTimeRange: { - start: 1520351200000, - end: 1520353700000 - } + it('Renders single IV table if no GW levels', () => { + store = configureStore({ + hydrographData: { + primaryIVData: TEST_PRIMARY_IV_DATA, + }, + hydrographState: { + selectedIVMethodID: '90649' } - }; - store = configureStore(testData); + }); + drawDataTables(testDiv, store); + expect(testDiv.select('#iv-table-container').size()).toBe(1); + expect(testDiv.select('#gw-table-container').size()).toBe(0); + }); + it('Renders single GW table if no IV data', () => { + store = configureStore({ + hydrographData: { + groundwaterLevels: TEST_GW_LEVELS + } + }); drawDataTables(testDiv, store); - expect(testDiv.selectAll('table').size()).toBe(1); - expect(testDiv.select('#gw-table-container').select('caption').text()).toEqual('Field visit data'); - expect(testDiv.select('tbody').selectAll('tr').size()).toBe(2); + + expect(testDiv.select('#iv-table-container').size()).toBe(0); + expect(testDiv.select('#gw-table-container').size()).toBe(1); }); -}); \ No newline at end of file +}); diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/discrete-data.js b/assets/src/scripts/monitoring-location/components/hydrograph/discrete-data.js index 5f0cdcc1501e353c3c51d37a8370101afcbadad5..dcd341420d5fafea58cc96b86be989e7a5d9f632 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/discrete-data.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/discrete-data.js @@ -12,9 +12,9 @@ const GW_LEVEL_CLASS = 'gw-level-point'; * @param {D3 scale } yScale */ export const drawGroundwaterLevels = function(svg, {levels, xScale, yScale, enableClip}) { - svg.selectAll('#iv-graph-gw-levels-group').remove(); + svg.selectAll('.iv-graph-gw-levels-group').remove(); const group = svg.append('g') - .attr('id', 'iv-graph-gw-levels-group'); + .attr('class', 'iv-graph-gw-levels-group'); if (enableClip) { group.attr('clip-path', 'url(#graph-clip)'); } diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/download-links.js b/assets/src/scripts/monitoring-location/components/hydrograph/download-links.js index 98b23fb91a95e77af6591dcc1712661380a365f8..4757bd51b4cc3916dcaa6c688a3effefb8a7b645 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/download-links.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/download-links.js @@ -1,6 +1,7 @@ /** * Module with functions for processing and structuring download link URLs */ +/* import {DateTime} from 'luxon'; import {createStructuredSelector} from 'reselect'; @@ -24,6 +25,7 @@ import {anyVisibleGroundwaterLevels} from './selectors/discrete-data'; * @param {String} timeSeriesType - one of two options, 'current' or 'compare' * @return {String} a URL usable to retrieve station data from WaterServices */ +/* const createUrlForDownloadLinks = function(currentIVDateRange, queryInformation, parameterCode, timeSeriesType) { let url = ''; const key = currentIVDateRange === 'P7D' ? `${timeSeriesType}:${currentIVDateRange}` : `${timeSeriesType}:${currentIVDateRange}:${parameterCode}`; @@ -51,7 +53,7 @@ const createUrlForDownloadLinks = function(currentIVDateRange, queryInformation, * @param {store} store - The Redux store, in the form of a JavaScript object * @param {String} siteno- a USGS numerical identifier for a specific monitoring location */ - +/* export const renderDownloadLinks = function(elem, store, siteno) { elem.call(link(store, (elem, { currentIVDateRange, @@ -154,3 +156,4 @@ export const renderDownloadLinks = function(elem, store, siteno) { requestTimeRange: getRequestTimeRange('current') }))); }; +*/ diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/download-links.test.js b/assets/src/scripts/monitoring-location/components/hydrograph/download-links.test.js index 0405bef1b6005f66c76f232047beb0cabb3b9905..9308c712106a594919f2b2f5b65d52cbdf6f7409 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/download-links.test.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/download-links.test.js @@ -6,7 +6,7 @@ import {configureStore} from 'ml/store'; import {renderDownloadLinks} from 'ivhydrograph/download-links'; -describe('monitoring-location/components/hydrograph/download-links', () => { +xdescribe('monitoring-location/components/hydrograph/download-links', () => { config.SERVICE_ROOT = 'https://fakeserviceroot.com'; config.GROUNDWATER_LEVELS_ENDPOINT = 'https://fakegroundwater.org/gw/'; @@ -547,4 +547,4 @@ describe('monitoring-location/components/hydrograph/download-links', () => { }); }); }); -}); \ No newline at end of file +}); diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/graph-brush.test.js b/assets/src/scripts/monitoring-location/components/hydrograph/graph-brush.test.js index 206834553e736b2035c20d20af92db87f942b231..d037745862c5897337d77dbdb9f48d164ed19d30 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/graph-brush.test.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/graph-brush.test.js @@ -5,153 +5,25 @@ import * as utils from 'ui/utils'; import{configureStore} from 'ml/store'; import {drawGraphBrush} from './graph-brush'; +import {TEST_CURRENT_TIME_RANGE, TEST_PRIMARY_IV_DATA, TEST_GW_LEVELS} from './mock-hydrograph-state'; describe ('monitoring-location/components/hydrograph/graph-brush module', () => { utils.mediaQuery = jest.fn().mockReturnValue(true); const TEST_STATE = { - ivTimeSeriesData: { - timeSeries: { - '00010:current': { - points: [{ - dateTime: 1514926800000, - value: 4, - qualifiers: ['P'] - }], - method: 'method1', - tsKey: 'current:P7D', - variable: '45807190' - }, - '00060:current': { - points: [{ - dateTime: 1514926800000, - value: 10, - qualifiers: ['P'] - }], - method: 'method1', - tsKey: 'current:P7D', - variable: '45807197' - }, - '00060:compare': { - points: [{ - dateTime: 1514926800000, - value: 10, - qualifiers: ['P'] - }], - method: 'method1', - tsKey: 'compare:P7D', - variable: '45807197' - } - }, - timeSeriesCollections: { - 'coll1': { - variable: '45807197', - timeSeries: ['00060:current'] - }, - 'coll2': { - variable: '45807197', - timeSeries: ['00060:compare'] - }, - 'coll3': { - variable: '45807197', - timeSeries: ['00060:median'] - }, - 'coll4': { - variable: '45807190', - timeSeries: ['00010:current'] - } - }, - queryInfo: { - 'current:P7D': { - notes: { - 'filter:timeRange': { - mode: 'PERIOD', - periodDays: 7 - }, - requestDT: 1522425600000 - } - } - }, - requests: { - 'current:P7D': { - timeSeriesCollections: ['coll1'] - }, - 'compare:P7D': { - timeSeriesCollections: ['coll2', 'col4'] - } - }, - variables: { - '45807197': { - variableCode: { - value: '00060' - }, - oid: '45807197', - variableName: 'Test title for 00060', - variableDescription: 'Test description for 00060', - unit: { - unitCode: 'unitCode' - } - }, - '45807190': { - variableCode: { - value: '00010' - }, - oid: '45807190', - unit: { - unitCode: 'unitCode' - } - } - }, - methods: { - 'method1': { - methodDescription: 'method description' - } - } + hydrographData: { + currentTimeRange: TEST_CURRENT_TIME_RANGE, + primaryIVData: TEST_PRIMARY_IV_DATA, + groundwaterLevels: TEST_GW_LEVELS }, - statisticsData : { - median: { - '00060': { - '1234': [ - { - month_nu: '2', - day_nu: '20', - p50_va: '40', - begin_yr: '1970', - end_yr: '2017', - loc_web_ds: 'This method' - }, { - month_nu: '2', - day_nu: '21', - p50_va: '41', - begin_yr: '1970', - end_yr: '2017', - loc_web_ds: 'This method' - }, { - month_nu: '2', - day_nu: '22', - p50_va: '42', - begin_yr: '1970', - end_yr: '2017', - loc_web_ds: 'This method' - } - ] - } - } - }, - ivTimeSeriesState: { - currentIVVariableID: '45807197', - currentIVDateRange: 'P7D', - showIVTimeSeries: { - current: true, - compare: true, - median: true - }, - loadingIVTSKeys: [] + hydrographState: { + selectedIVMethodID: '90649' }, ui: { - width: 400 + width: 900 } }; + describe('drawGraphBrush', () => { let div, store; @@ -177,10 +49,11 @@ describe ('monitoring-location/components/hydrograph/graph-brush module', () => expect(div.selectAll('.tick').size()).toBe(7); }); - it('Should create a time-series-line, and an x-axis', () => { + it('Should create a time-series-line, gw levels circles, and an x-axis', () => { div.call(drawGraphBrush, store); - expect(div.selectAll('#ts-current-group').size()).toBe(1); + expect(div.selectAll('.ts-primary-group').size()).toBe(1); + expect(div.selectAll('.iv-graph-gw-levels-group').size()).toBe(1); expect(div.selectAll('.x-axis').size()).toBe(1); }); }); diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/graph-controls.js b/assets/src/scripts/monitoring-location/components/hydrograph/graph-controls.js index aeb3d58aae882d2a8f4e99d6fa2e8e2f7d81f1cc..e5512d56e460fcc9dcc89f430517ff6b8d097a9b 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/graph-controls.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/graph-controls.js @@ -1,8 +1,6 @@ import {link} from 'ui/lib/d3-redux'; -import {getInputsForRetrieval} from 'ml/selectors/hydrograph-state-selector'; - import {getSelectedParameterCode} from 'ml/selectors/hydrograph-state-selector'; import {getTimeRange} from 'ml/selectors/hydrograph-data-selector'; @@ -12,11 +10,10 @@ import {setCompareDataVisibility, setMedianDataVisibility} from 'ml/store/hydrog import {isVisible} from './selectors/time-series-data'; /* - * Create the show audible toggle, last year toggle, and median toggle for the time series graph. + * Create the last year toggle, and median toggle for the time series graph. * @param {Object} elem - D3 selection */ export const drawGraphControls = function(elem, store, siteno) { - const graphControlDiv = elem.append('ul') .classed('usa-fieldset', true) .classed('usa-list--unstyled', true) @@ -37,11 +34,13 @@ export const drawGraphControls = function(elem, store, siteno) { const state = store.getState(); const currentTimeRange = getTimeRange('current')(state); store.dispatch(setCompareDataVisibility(this.checked)); - store.dispatch(retrievePriorYearIVData(siteno, { - parameterCode: getSelectedParameterCode(state), - startTime: currentTimeRange.start, - endTime: currentTimeRange.end - })); + if (this.checked) { + store.dispatch(retrievePriorYearIVData(siteno, { + parameterCode: getSelectedParameterCode(state), + startTime: currentTimeRange.start, + endTime: currentTimeRange.end + })); + } }) // Sets the state of the toggle .call(link(store,function(elem, checked) { @@ -66,7 +65,9 @@ export const drawGraphControls = function(elem, store, siteno) { .attr('ga-event-action', 'toggleMedian') .on('click', function() { store.dispatch(setMedianDataVisibility(this.checked)); - store.dispatch(retrieveMedianStatistics(siteno, getSelectedParameterCode(store.getState()))); + if (this.checked) { + store.dispatch(retrieveMedianStatistics(siteno, getSelectedParameterCode(store.getState()))); + } }) // Sets the state of the toggle .call(link(store,function(elem, checked) { diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/graph-controls.test.js b/assets/src/scripts/monitoring-location/components/hydrograph/graph-controls.test.js index afbec8e9de71e5fbca1bb3710f579305dd49bf79..a3342b13ebd8ae917c47bc373ee39ff122608a7d 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/graph-controls.test.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/graph-controls.test.js @@ -1,236 +1,101 @@ import {select} from 'd3-selection'; +import sinon from 'sinon'; import {configureStore} from 'ml/store'; -import {Actions} from 'ml/store/instantaneous-value-time-series-state'; +import * as hydrographData from 'ml/store/hydrograph-data'; import {drawGraphControls} from './graph-controls'; +import {TEST_CURRENT_TIME_RANGE} from './mock-hydrograph-state'; // Tests for the graph-controls module describe('monitoring-location/components/hydrograph/graph-controls', () => { - - const TEST_STATE = { - ivTimeSeriesData: { - timeSeries: { - '00010:current': { - points: [{ - dateTime: 1514926800000, - value: 4, - qualifiers: ['P'] - }], - method: 'method1', - tsKey: 'current:P7D', - variable: '45807190' - }, - '00060:current': { - points: [{ - dateTime: 1514926800000, - value: 10, - qualifiers: ['P'] - }], - method: 'method1', - tsKey: 'current:P7D', - variable: '45807197' - }, - '00060:compare': { - points: [{ - dateTime: 1514926800000, - value: 10, - qualifiers: ['P'] - }], - method: 'method1', - tsKey: 'compare:P7D', - variable: '45807197' - } - }, - timeSeriesCollections: { - 'coll1': { - variable: '45807197', - timeSeries: ['00060:current'] - }, - 'coll2': { - variable: '45807197', - timeSeries: ['00060:compare'] - }, - 'coll3': { - variable: '45807197', - timeSeries: ['00060:median'] - }, - 'coll4': { - variable: '45807190', - timeSeries: ['00010:current'] - } - }, - queryInfo: { - 'current:P7D': { - notes: { - 'filter:timeRange': { - mode: 'PERIOD', - periodDays: 7 - }, - requestDT: 1522425600000 - } - } - }, - requests: { - 'current:P7D': { - timeSeriesCollections: ['coll1'] - }, - 'compare:P7D': { - timeSeriesCollections: ['coll2', 'col4'] - } - }, - variables: { - '45807197': { - variableCode: { - value: '00060' - }, - oid: '45807197', - variableName: 'Test title for 00060', - variableDescription: 'Test description for 00060', - unit: { - unitCode: 'unitCode' - } - }, - '45807190': { - variableCode: { - value: '00010' - }, - oid: '45807190', - unit: { - unitCode: 'unitCode' - } - } - }, - methods: { - 'method1': { - methodDescription: 'method description' - } - } - }, - statisticsData : { - median: { - '00060': { - '1234': [ - { - month_nu: '2', - day_nu: '20', - p50_va: '40', - begin_yr: '1970', - end_yr: '2017', - loc_web_ds: 'This method' - }, { - month_nu: '2', - day_nu: '21', - p50_va: '41', - begin_yr: '1970', - end_yr: '2017', - loc_web_ds: 'This method' - }, { - month_nu: '2', - day_nu: '22', - p50_va: '42', - begin_yr: '1970', - end_yr: '2017', - loc_web_ds: 'This method' - } - ] - } - } - }, - ivTimeSeriesState: { - currentIVVariableID: '45807197', - currentIVDateRange: 'P7D', - showIVTimeSeries: { - current: true, - compare: true, - median: true - }, - loadingIVTSKeys: [] - }, - ui: { - width: 400 - } - }; - describe('drawGraphControls', () => { let div; + let fakeServer; let store; + let retrievePriorYearSpy, retrieveMedianStatisticsSpy; beforeEach(() => { div = select('body').append('div'); - store = configureStore(TEST_STATE); - div.call(drawGraphControls, store); + store = configureStore({ + hydrographData: { + currentTimeRange: TEST_CURRENT_TIME_RANGE + }, + hydrographState: { + showCompareIVData: false, + showMedianData: false, + selectedParameterCode: '72019' + } + }); + fakeServer = sinon.createFakeServer(); + retrievePriorYearSpy = jest.spyOn(hydrographData, 'retrievePriorYearIVData'); + retrieveMedianStatisticsSpy = jest.spyOn(hydrographData, 'retrieveMedianStatistics'); + + drawGraphControls(div, store, '11112222'); }); afterEach(() => { + fakeServer.restore(); div.remove(); }); // last year checkbox tests - it('Should render the compare toggle checked', () => { + it('Should render the compare toggle unchecked', () => { const checkbox = select('#last-year-checkbox'); expect(checkbox.size()).toBe(1); - expect(checkbox.property('checked')).toBe(true); + expect(checkbox.property('checked')).toBe(false); }); - it('Should render the compare toggle unchecked', () => { - store.dispatch(Actions.setIVTimeSeriesVisibility('compare', false)); - return new Promise(resolve => { - window.requestAnimationFrame(() => { - const checkbox = select('#last-year-checkbox'); - expect(checkbox.size()).toBe(1); - expect(checkbox.property('checked')).toBe(false); - resolve(); - }); + it('Should set the compare visibility to true and retrieve the Prior year data', () => { + const checkbox = select('#last-year-checkbox'); + checkbox.property('checked', true); + checkbox.dispatch('click'); + + expect(store.getState().hydrographState.showCompareIVData).toBe(true); + expect(retrievePriorYearSpy).toHaveBeenCalledWith('11112222', { + parameterCode: '72019', + startTime: TEST_CURRENT_TIME_RANGE.start, + endTime: TEST_CURRENT_TIME_RANGE.end }); }); - it('should be enabled if there are last year data', () => { - expect(select('#last-year-checkbox').property('disabled')).toBeFalsy(); - }); + it('Changing the compare visibility back to false should set the visibility but not retrieve the Prior year data', () => { + const checkbox = select('#last-year-checkbox'); + checkbox.property('checked', true); + checkbox.dispatch('click'); - it('should be disabled if there are no last year data', () => { - store.dispatch(Actions.setCurrentIVVariable('45807190')); - return new Promise(resolve => { - window.requestAnimationFrame(() => { - expect(select('#last-year-checkbox').property('disabled')).toBeTruthy(); - resolve(); - }); - }); + checkbox.property('checked', false); + checkbox.dispatch('click'); + expect(store.getState().hydrographState.showCompareIVData).toBe(false); + expect(retrievePriorYearSpy.mock.calls).toHaveLength(1); }); - // median checkbox tests - it('Should render the median toggle checked', () => { + //median visibility tests + it('Should render the median toggle unchecked', () => { const checkbox = select('#median-checkbox'); expect(checkbox.size()).toBe(1); - expect(checkbox.property('checked')).toBe(true); + expect(checkbox.property('checked')).toBe(false); }); - it('Should render the median toggle unchecked', () => { - store.dispatch(Actions.setIVTimeSeriesVisibility('median', false)); - return new Promise(resolve => { - window.requestAnimationFrame(() => { - const checkbox = select('#median-checkbox'); - expect(checkbox.size()).toBe(1); - expect(checkbox.property('checked')).toBe(false); - resolve(); - }); - }); - }); + it('Should set the median visibility to true and retrieve the median statistics data', () => { + const checkbox = select('#median-checkbox'); + checkbox.property('checked', true); + checkbox.dispatch('click'); - it('should be enabled if there are median statistics data', () => { - expect(select('#median-checkbox').property('disabled')).toBeFalsy(); + expect(store.getState().hydrographState.showMedianData).toBe(true); + expect(retrieveMedianStatisticsSpy).toHaveBeenCalledWith('11112222', '72019'); }); - it('should be disabled if there are no median statistics data', () => { - store.dispatch(Actions.setCurrentIVVariable('45807190')); - return new Promise(resolve => { - window.requestAnimationFrame(() => { - expect(select('#median-checkbox').property('disabled')).toBeTruthy(); - resolve(); - }); - }); + it('Changing the median visibility back to false should set the visibility but not retrieve the median data', () => { + const checkbox = select('#median-checkbox'); + checkbox.property('checked', true); + checkbox.dispatch('click'); + + checkbox.property('checked', false); + checkbox.dispatch('click'); + expect(store.getState().hydrographState.showMedianData).toBe(false); + expect(retrieveMedianStatisticsSpy.mock.calls).toHaveLength(1); }); }); }); diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/index.js b/assets/src/scripts/monitoring-location/components/hydrograph/index.js index 2e88f3ebca5095c8e64ef493a2abdcec43d433eb..c4a0ff16d354f03931794c66d2f006bc2688c050 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/index.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/index.js @@ -102,7 +102,6 @@ export const attachToNode = function(store, // Fetch waterwatch flood levels - TODO: consider only fetching when gage height is requested store.dispatch(floodDataActions.retrieveWaterwatchData(siteno)); - fetchDataPromise.then(() => { nodeElem .select('.loading-indicator-container') diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/index.test.js b/assets/src/scripts/monitoring-location/components/hydrograph/index.test.js index 9423a4af81ecb3c53416fe7bbe380a183f24f23e..d9a3a8aa4d7388a9f38e28b8078b1b9a52aad753 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/index.test.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/index.test.js @@ -4,175 +4,26 @@ import sinon from 'sinon'; import * as utils from 'ui/utils'; import config from 'ui/config'; + import {configureStore} from 'ml/store'; -import {Actions as ivTimeSeriesDataActions} from 'ml/store/instantaneous-value-time-series-data'; -import {Actions as statisticsDataActions} from 'ml/store/statistics-data'; -import {Actions as timeZoneActions} from 'ml/store/time-zone'; import {Actions as floodDataActions} from 'ml/store/flood-inundation'; +import * as hydrographData from 'ml/store/hydrograph-data'; +import * as hydrographParameters from 'ml/store/hydrograph-parameters'; import {attachToNode} from './index'; - -const TEST_STATE = { - ivTimeSeriesData: { - timeSeries: { - 'method1:00010:current': { - points: [{ - dateTime: 1514926800000, - value: 4, - qualifiers: ['P'] - }], - method: 'method1', - tsKey: 'current:P7D', - variable: '45807190' - }, - 'method1:00060:current': { - points: [{ - dateTime: 1514926800000, - value: 10, - qualifiers: ['P'] - }], - method: 'method1', - tsKey: 'current:P7D', - variable: '45807197' - }, - 'method1:00060:compare': { - points: [{ - dateTime: 1514926800000, - value: 10, - qualifiers: ['P'] - }], - method: 'method1', - tsKey: 'compare:P7D', - variable: '45807197' - } - }, - timeSeriesCollections: { - 'coll1': { - variable: '45807197', - timeSeries: ['00060:current'] - }, - 'coll2': { - variable: '45807197', - timeSeries: ['00060:compare'] - }, - 'coll3': { - variable: '45807197', - timeSeries: ['00060:median'] - }, - 'coll4': { - variable: '45807190', - timeSeries: ['00010:current'] - } - }, - queryInfo: { - 'current:P7D': { - queryURL: 'http://waterservices.usgs.gov/nwis/iv/sites=05413500&period=P7D&siteStatus=all&format=json', - notes: { - 'filter:timeRange': { - mode: 'PERIOD', - periodDays: 7 - }, - requestDT: 1522425600000 - } - } - }, - requests: { - 'current:P7D': { - timeSeriesCollections: ['coll1'] - }, - 'compare:P7D': { - timeSeriesCollections: ['coll2', 'col4'] - } - }, - variables: { - '45807197': { - variableCode: { - value: '00060' - }, - oid: '45807197', - variableName: 'Test title for 00060', - variableDescription: 'Test description for 00060', - unit: { - unitCode: 'unitCode' - } - }, - '45807190': { - variableCode: { - value: '00010' - }, - oid: '45807190', - variableName: 'Test title for 00010', - variableDescription: 'Test description for 00010', - unit: { - unitCode: 'unitCode' - } - } - }, - methods: { - 'method1': { - methodDescription: 'method description' - } - } - }, - statisticsData : { - median: { - '00060': { - '1234': [ - { - month_nu: '2', - day_nu: '20', - p50_va: '40', - begin_yr: '1970', - end_yr: '2017', - loc_web_ds: 'This method' - }, { - month_nu: '2', - day_nu: '21', - p50_va: '41', - begin_yr: '1970', - end_yr: '2017', - loc_web_ds: 'This method' - }, { - month_nu: '2', - day_nu: '22', - p50_va: '42', - begin_yr: '1970', - end_yr: '2017', - loc_web_ds: 'This method' - } - ] - } - } - }, - ivTimeSeriesState: { - currentIVVariableID: '45807197', - currentIVDateRange: 'P7D', - showIVTimeSeries: { - current: true, - compare: true, - median: true - }, - loadingIVTSKeys: [] - }, - ui: { - width: 400 - }, - floodData: { - floodLevels: { - site_no: '07144100', - action_stage: '20', - flood_stage: '22', - moderate_flood_stage: '25', - major_flood_stage: '26' - } - } -}; +import { + TEST_CURRENT_TIME_RANGE, + TEST_GW_LEVELS, + TEST_HYDROGRAPH_PARAMETERS, TEST_MEDIAN_DATA, + TEST_PRIMARY_IV_DATA +} from './mock-hydrograph-state'; describe('monitoring-location/components/hydrograph module', () => { utils.mediaQuery = jest.fn().mockReturnValue(true); utils.wrap = jest.fn(); + config.locationTimeZone = 'America/Chicago'; config.ivPeriodOfRecord = { - '00010': { + '72019': { begin_date: '01-02-2001', end_date: '10-15-2015' }, @@ -190,14 +41,28 @@ describe('monitoring-location/components/hydrograph module', () => { } }; + config.igwPeriodOfRecord = { + '72019': { + begin_date: '01-02-2000', + end_date: '10-15-2015' + } + }; + let graphNode; let fakeServer; - let loadPromise = new Promise(() => null); + let nodeElem; + + const INITIAL_PARAMETERS = { + siteno: '11112222', + agencyCode: 'USGS', + sitename: 'Site name', + parameterCode: '72019' + }; beforeEach(() => { let body = select('body'); body.append('a') - .attr('id','classic-page-link') + .attr('id', 'classic-page-link') .attr('href', 'https://fakeserver/link'); let component = body.append('div') .attr('id', 'hydrograph'); @@ -207,6 +72,7 @@ describe('monitoring-location/components/hydrograph module', () => { component.append('div').attr('id', 'iv-data-table-container'); graphNode = document.getElementById('hydrograph'); + nodeElem = select(graphNode); fakeServer = sinon.createFakeServer(); }); @@ -217,390 +83,220 @@ describe('monitoring-location/components/hydrograph module', () => { select('#classic-page-link').remove(); }); - it('expect alert if no siteno defined', () => { - attachToNode({}, graphNode, {}, loadPromise); - expect(graphNode.innerHTML).toContain('No data is available'); - }); - - describe('Tests for initial data fetching when showOnlyGraph is false (the default)', () => { + describe('Tests for initial data fetching and setting', () => { let store; + let retrieveHydrographDataSpy, retrieveHydrographParametersSpy, retrieveWaterwatchDataSpy; beforeEach(() => { store = configureStore({ - ivTimeSeriesState: { - loadingIVTSKeys: [] + hydrographData: {}, + hydrographState: {}, + hydrographParameters: {}, + floodData: {}, + ui: { + width: 1000 } }); + retrieveHydrographDataSpy = jest.spyOn(hydrographData, 'retrieveHydrographData'); + retrieveHydrographParametersSpy = jest.spyOn(hydrographParameters, 'retrieveHydrographParameters'); + retrieveWaterwatchDataSpy = jest.spyOn(floodDataActions, 'retrieveWaterwatchData'); }); - it('loading-indicator is shown until initial data has been retrieved', () => { - attachToNode(store, graphNode, { - siteno: '12345678' - }, loadPromise); - - expect(select(graphNode).select('.loading-indicator').size()).toBe(1); - }); - - it('Expects retrieveIanaTimeZone to be called', () => { - jest.spyOn(timeZoneActions, 'retrieveIanaTimeZone'); - attachToNode(store, graphNode, { - siteno: '12345678' - }, loadPromise); - - expect(timeZoneActions.retrieveIanaTimeZone).toHaveBeenCalled(); + it('Loading indicator should be shown', () => { + attachToNode(store, graphNode, INITIAL_PARAMETERS); + expect(nodeElem.select('.loading-indicator').size()).toBe(1); }); - describe('Always retrieve the 7 day data and median statistics', () => { - - beforeEach(() => { - jest.spyOn(ivTimeSeriesDataActions, 'retrieveIVTimeSeries'); - jest.spyOn(statisticsDataActions, 'retrieveMedianStatistics'); - }); - - it('Retrieve if no date parameters are used', () => { - attachToNode(store, graphNode, { - siteno: '12345678', - parameterCode: '00065' - }, loadPromise); - expect(ivTimeSeriesDataActions.retrieveIVTimeSeries).toHaveBeenCalledWith('12345678'); - expect(statisticsDataActions.retrieveMedianStatistics).toHaveBeenCalledWith('12345678'); + describe('Fetching initial hydrograph data', () => { + it('With just parameter code set', () => { + attachToNode(store, graphNode, INITIAL_PARAMETERS); + expect(retrieveHydrographDataSpy).toHaveBeenCalledWith('11112222', { + parameterCode: '72019', + period: 'P7D', + startTime: null, + endTime: null, + loadCompare: undefined, + loadMedian: false + }); + expect(store.getState().hydrographState).toEqual({ + selectedParameterCode: '72019', + selectedDateRange: 'P7D', + showCompareIVData: undefined, + selectedIVMethodID: undefined + }); }); - it('Retrieve if period parameters is used', () => { + it('With custom period', () => { attachToNode(store, graphNode, { - siteno: '12345678', - parameterCode: '00065', - period: 'P30D' - }, loadPromise); - - expect(ivTimeSeriesDataActions.retrieveIVTimeSeries).toHaveBeenCalledWith('12345678'); - expect(statisticsDataActions.retrieveMedianStatistics).toHaveBeenCalledWith('12345678'); + ...INITIAL_PARAMETERS, + period: 'P45D' + }); + expect(retrieveHydrographDataSpy).toHaveBeenCalledWith('11112222', { + parameterCode: '72019', + period: 'P45D', + startTime: null, + endTime: null, + loadCompare: undefined, + loadMedian: false + }); + expect(store.getState().hydrographState).toEqual({ + selectedParameterCode: '72019', + selectedDateRange: 'P45D', + showCompareIVData: undefined, + selectedIVMethodID: undefined + }); }); - it('Retrieve if startDT and endDT parameters are used', () => { + it('With custom time range', () => { attachToNode(store, graphNode, { - siteno: '12345678', - parameterCode: '00065', - startDT: '2010-01-01', - endDT: '2010-03-01' - }, loadPromise); - - expect(ivTimeSeriesDataActions.retrieveIVTimeSeries).toHaveBeenCalledWith('12345678'); - expect(statisticsDataActions.retrieveMedianStatistics).toHaveBeenCalledWith('12345678'); - }); - }); - - describe('Retrieve additional data if indicated', () => { - beforeEach(() => { - jest.spyOn(ivTimeSeriesDataActions, 'retrieveIVTimeSeries').mockReturnValue(function() { - return Promise.resolve({}); + ...INITIAL_PARAMETERS, + startDT: '2020-02-01', + endDT: '2020-02-15' }); - jest.spyOn(ivTimeSeriesDataActions, 'retrieveExtendedIVTimeSeries').mockReturnValue(function() { - return Promise.resolve({}); + expect(retrieveHydrographDataSpy).toHaveBeenCalledWith('11112222', { + parameterCode: '72019', + period: null, + startTime: '2020-02-01T00:00:00.000-06:00', + endTime: '2020-02-15T23:59:59.999-06:00', + loadCompare: undefined, + loadMedian: false }); - jest.spyOn(ivTimeSeriesDataActions, 'retrieveUserRequestedIVDataForDateRange').mockReturnValue(function() { - return Promise.resolve({}); + expect(store.getState().hydrographState).toEqual({ + selectedParameterCode: '72019', + selectedDateRange: 'custom', + selectedCustomDateRange: { + start: '2020-02-01', + end: '2020-02-15' + }, + showCompareIVData: undefined, + selectedIVMethodID: undefined }); }); - it('Expect to not retrieve additional time series if not indicated', () => { + it('With compare enabled', () => { attachToNode(store, graphNode, { - siteno: '12345678', - parameterCode: '00065' - }, loadPromise); - - return new Promise(resolve => { - window.requestAnimationFrame(() => { - expect(ivTimeSeriesDataActions.retrieveExtendedIVTimeSeries).not.toHaveBeenCalled(); - expect(ivTimeSeriesDataActions.retrieveUserRequestedIVDataForDateRange).not.toHaveBeenCalled(); - resolve(); - }); + ...INITIAL_PARAMETERS, + compare: true }); - }); - it('should retrieve extend time series if period set', () => { - attachToNode(store, graphNode, { - siteno: '12345678', - parameterCode: '00065', - period: 'P30D' - }, loadPromise); - - return new Promise(resolve => { - window.requestAnimationFrame(() => { - expect(ivTimeSeriesDataActions.retrieveExtendedIVTimeSeries).toHaveBeenCalledWith('12345678', 'P30D', '00065'); - expect(ivTimeSeriesDataActions.retrieveUserRequestedIVDataForDateRange).not.toHaveBeenCalled(); - resolve(); - }); + expect(retrieveHydrographDataSpy).toHaveBeenCalledWith('11112222', { + parameterCode: '72019', + period: 'P7D', + startTime: null, + endTime: null, + loadCompare: true, + loadMedian: false + }); + expect(store.getState().hydrographState).toEqual({ + selectedParameterCode: '72019', + selectedDateRange: 'P7D', + showCompareIVData: true, + selectedIVMethodID: undefined }); }); - it('should not retrieve data for date range if time zone has not been fetched', () => { + it('With timeSeriesId set', () => { attachToNode(store, graphNode, { - siteno: '12345678', - parameterCode: '00065', - startDT: '2010-01-01', - endDT: '2010-03-01' - }, loadPromise); - - return new Promise(resolve => { - window.requestAnimationFrame(() => { - expect(ivTimeSeriesDataActions.retrieveExtendedIVTimeSeries).not.toHaveBeenCalled(); - expect(ivTimeSeriesDataActions.retrieveUserRequestedIVDataForDateRange).not.toHaveBeenCalled(); - resolve(); - }); + ...INITIAL_PARAMETERS, + timeSeriesId: '90649' }); - }); - - it('should retrieve data for date range if time zone has been fetched', () => { - jest.spyOn(timeZoneActions, 'retrieveIanaTimeZone').mockReturnValue(function() { - return Promise.resolve({}); + expect(retrieveHydrographDataSpy).toHaveBeenCalledWith('11112222', { + parameterCode: '72019', + period: 'P7D', + startTime: null, + endTime: null, + loadCompare: undefined, + loadMedian: false }); - attachToNode(store, graphNode, { - siteno: '12345678', - parameterCode: '00065', - startDT: '2010-01-01', - endDT: '2010-03-01' - }, loadPromise); - - return new Promise(resolve => { - window.requestAnimationFrame(() => { - expect(ivTimeSeriesDataActions.retrieveExtendedIVTimeSeries).not. toHaveBeenCalled(); - expect(ivTimeSeriesDataActions.retrieveUserRequestedIVDataForDateRange).toHaveBeenCalledWith('12345678', '2010-01-01', '2010-03-01', '00065'); - resolve(); - }); + expect(store.getState().hydrographState).toEqual({ + selectedParameterCode: '72019', + selectedDateRange: 'P7D', + showCompareIVData: undefined, + selectedIVMethodID: '90649' }); }); }); - }); - - describe('Tests for initial data fetching when showOnlyGraph is true', () => { - let store; - beforeEach(() => { - store = configureStore({ - ivTimeSeriesState: { - loadingIVTSKeys: [] - } - }); - - jest.spyOn(ivTimeSeriesDataActions, 'retrieveIVTimeSeries').mockReturnValue(function() { - return Promise.resolve({}); - }); - jest.spyOn(ivTimeSeriesDataActions, 'retrieveCustomTimePeriodIVTimeSeries').mockReturnValue(function() { - return Promise.resolve({}); - }); - jest.spyOn(ivTimeSeriesDataActions, 'retrieveUserRequestedIVDataForDateRange').mockReturnValue(function() { - return Promise.resolve({}); - }); + it('Should fetch the hydrograph parameters', () => { + attachToNode(store, graphNode, INITIAL_PARAMETERS); + expect(retrieveHydrographParametersSpy).toHaveBeenCalledWith('11112222'); }); - it('should retrieve custom time period if period is specified', () => { - attachToNode(store, graphNode, { - siteno: '12345678', - parameterCode: '00065', - period: 'P20D', - showOnlyGraph: true - }, loadPromise); - - return new Promise(resolve => { - window.requestAnimationFrame(() => { - expect(ivTimeSeriesDataActions.retrieveIVTimeSeries).not.toHaveBeenCalled(); - expect(ivTimeSeriesDataActions.retrieveCustomTimePeriodIVTimeSeries).toHaveBeenCalledWith('12345678', '00065', 'P20D'); - expect(ivTimeSeriesDataActions.retrieveUserRequestedIVDataForDateRange).not.toHaveBeenCalled(); - - resolve(); - }); - }); + it('Should fetch the waterwatch flood levels', () => { + attachToNode(store, graphNode, INITIAL_PARAMETERS); + expect(retrieveWaterwatchDataSpy).toHaveBeenCalledWith('11112222'); }); - it('should not retrieve date range for date range parameters if time zone has not been fetched', () => { + it('Should fetch the data but not set the hydrograph state or fetch hydrograph parameters when showOnlyGraph is true', () => { attachToNode(store, graphNode, { - siteno: '12345678', - parameterCode: '00065', - startDT: '2010-01-01', - endDT: '2010-03-01', + ...INITIAL_PARAMETERS, showOnlyGraph: true - }, loadPromise); - - return new Promise(resolve => { - window.requestAnimationFrame(() => { - expect(ivTimeSeriesDataActions.retrieveIVTimeSeries).not.toHaveBeenCalled(); - expect(ivTimeSeriesDataActions.retrieveCustomTimePeriodIVTimeSeries).not.toHaveBeenCalled(); - expect(ivTimeSeriesDataActions.retrieveUserRequestedIVDataForDateRange).not.toHaveBeenCalled(); - resolve(); - }); }); - }); - it('should retrieve date range for date range parameters if time zone has been fetched', () => { - jest.spyOn(timeZoneActions, 'retrieveIanaTimeZone').mockReturnValue(function() { - return Promise.resolve({}); - }); - attachToNode(store, graphNode, { - siteno: '12345678', - parameterCode: '00065', - startDT: '2010-01-01', - endDT: '2010-03-01', - showOnlyGraph: true - }, loadPromise); - - return new Promise(resolve => { - window.requestAnimationFrame(() => { - expect(ivTimeSeriesDataActions.retrieveIVTimeSeries).not.toHaveBeenCalled(); - expect(ivTimeSeriesDataActions.retrieveCustomTimePeriodIVTimeSeries).not.toHaveBeenCalled(); - expect(ivTimeSeriesDataActions.retrieveUserRequestedIVDataForDateRange).toHaveBeenCalledWith('12345678', '2010-01-01', '2010-03-01', '00065'); - resolve(); - }); - }); - }); - - it('should retrieve time series if no custom period or date range', () => { - attachToNode(store, graphNode, { - siteno: '12345678', - parameterCode: '00065', - showOnlyGraph: true - }, loadPromise); - - return new Promise(resolve => { - window.requestAnimationFrame(() => { - expect(ivTimeSeriesDataActions.retrieveIVTimeSeries).toHaveBeenCalledWith('12345678', ['00065']); - expect(ivTimeSeriesDataActions.retrieveCustomTimePeriodIVTimeSeries).not.toHaveBeenCalled(); - expect(ivTimeSeriesDataActions.retrieveUserRequestedIVDataForDateRange).not.toHaveBeenCalled(); - resolve(); - }); + expect(retrieveHydrographDataSpy).toHaveBeenCalledWith('11112222', { + parameterCode: '72019', + period: 'P7D', + startTime: null, + endTime: null, + loadCompare: undefined, + loadMedian: false }); + expect(store.getState().hydrographState).toEqual({}); + expect(retrieveWaterwatchDataSpy).toHaveBeenCalled(); + expect(retrieveHydrographParametersSpy).not.toHaveBeenCalled(); }); }); - describe('graphNode contains the expected elements when no IV time series has been retrieved and showOnlyGraph is false', () => { + describe('Tests for rendering once fetching is complete when showOnlyGraph is false', () => { let store; - config.NWIS_INVENTORY_PAGE_URL = 'https://fakenwis.usgs.gov/inventory'; - let resolvedLoadPromise = Promise.resolve(); beforeEach(() => { - jest.spyOn(floodDataActions, 'retrieveWaterwatchData').mockReturnValue(function() { - return Promise.resolve({}); - }); - jest.spyOn(ivTimeSeriesDataActions, 'retrieveIVTimeSeries').mockReturnValue(function() { - return Promise.resolve({}); - }); store = configureStore({ - ...TEST_STATE, - ivTimeSeriesData: {}, - ivTimeSeriesState: { - ...TEST_STATE.ivTimeSeriesState, - currentIVVariableID: '', - currentIVDateRange: '' - + hydrographData: { + primaryIVData: TEST_PRIMARY_IV_DATA, + currentTimeRange: TEST_CURRENT_TIME_RANGE, + groundwaterLevels: TEST_GW_LEVELS, + medianStatisticsData: TEST_MEDIAN_DATA }, + hydrographState: { + selectedIVMethodID: '90649', + showMedianData: true + }, + hydrographParameters: TEST_HYDROGRAPH_PARAMETERS, + floodData: {}, ui: { - windowWidth: 400, - width: 400 + width: 1000 } }); - attachToNode(store, graphNode, {siteno: '12345678'}, resolvedLoadPromise); - return new Promise(resolve => { - window.requestAnimationFrame(() => { - resolve(); - }); - }); - }); - - it('should show info alert', () => { - expect(select('.usa-alert--info').size()).toBe(1); - }); - it('should use inventory for classic page link', () => { - expect(select('#classic-page-link').attr('href')).toContain('https://fakenwis.usgs.gov/inventory'); - }); - }); - - describe('graphNode contains the expected elements when showOnlyGraph is false', () => { - /* eslint no-use-before-define: 0 */ - let store; - let resolvedLoadPromise = Promise.resolve(); - beforeEach(() => { - jest.spyOn(floodDataActions, 'retrieveWaterwatchData').mockReturnValue(function() { - return Promise.resolve({}); + hydrographData.retrieveHydrographData = jest.fn(() => { + return function() { + return Promise.resolve(); + }; }); - jest.spyOn(ivTimeSeriesDataActions, 'retrieveIVTimeSeries').mockReturnValue(function() { - return Promise.resolve({}); - }); - store = configureStore({ - ...TEST_STATE, - ivTimeSeriesData: { - ...TEST_STATE.ivTimeSeriesData, - timeSeries: { - ...TEST_STATE.ivTimeSeriesData.timeSeries, - 'method1:00060:current': { - ...TEST_STATE.ivTimeSeriesData.timeSeries['method1:00060:current'], - points: [{ - dateTime: 1514926800000, - value: 10, - qualifiers: ['P'] - }, { - dateTime: 1514930400000, - value: null, - qualifiers: ['P', 'FLD'] - }] - } - } - }, - ivTimeSeriesState: { - showIVTimeSeries: { - current: true, - compare: true, - median: true - }, - currentIVVariableID: '45807197', - currentIVDateRange: 'P7D', - currentIVMethodID: 'method1', - loadingIVTSKeys: [], - ivGraphBrushOffset: null, - userInputsForTimeRange: { - mainTimeRangeSelectionButton: 'P7D', - customTimeRangeSelectionButton: 'days-input', - numberOfDaysFieldValue: '' - } - }, - ui: { - windowWidth: 400, - width: 400 - } + hydrographParameters.retrieveHydrographParameters = jest.fn(() => { + return function() { + return Promise.resolve(); + }; }); - - attachToNode(store, graphNode, {siteno: '12345678'}, resolvedLoadPromise); - return new Promise(resolve => { - window.requestAnimationFrame(() => { - resolve(); - }); + attachToNode(store, graphNode, { + ...INITIAL_PARAMETERS, + showOnlyGraph: false }); }); - it('should not show info alert', () => { - expect(select('.usa-alert--info').size()).toBe(0); - }); - - it('should not use inventory for classic page link', () => { - expect(select('#classic-page-link').attr('href')).not.toContain('https://fakenwis.usgs.gov/inventory'); + it('loading indicator should be hidden', () => { + expect(nodeElem.select('.loading-indicator').size()).toBe(0); }); it('should render the correct number of svg nodes', () => { - return new Promise(resolve => { - window.requestAnimationFrame(() => { - window.requestAnimationFrame(() => { - // one main hydrograph, brush, slider, legend and two sparklines - expect(selectAll('svg').size()).toBe(6); - resolve(); - }); - }); - }); + expect(selectAll('svg').size()).toBe(4); }); it('should have a title div', () => { const titleDiv = selectAll('.time-series-graph-title'); expect(titleDiv.size()).toBe(1); - expect(titleDiv.select('div').text()).toContain('Test title for 00060, method description'); - expect(titleDiv.select('.usa-tooltip').text()).toEqual('Test description for 00060'); + expect(titleDiv.select('div').text()).toContain('Depth to water level'); + expect(titleDiv.select('.usa-tooltip').text()).toEqual('Depth to water level, feet'); }); it('should have a defs node', () => { @@ -610,13 +306,12 @@ describe('monitoring-location/components/hydrograph module', () => { }); it('should render time series data as a line', () => { - // There should be one segment per time-series. Each is a single - // point, so should be a circle. - expect(selectAll('.hydrograph-svg .line-segment').size()).toBe(2); + // There should be four segments + expect(selectAll('.hydrograph-svg .line-segment').size()).toBe(4); }); it('should render a rectangle for masked data', () => { - expect(selectAll('.hydrograph-svg g.current-mask-group').size()).toBe(1); + expect(selectAll('.hydrograph-svg g.iv-mask-group').size()).toBe(1); }); it('should have a point for the median stat data with a label', () => { @@ -647,7 +342,7 @@ describe('monitoring-location/components/hydrograph module', () => { it('should have tooltips for the select series table', () => { // one for each of the two parameters and the WaterAlert links - expect(selectAll('table .usa-tooltip').size()).toBe(4); + expect(selectAll('table .usa-tooltip').size()).toBe(10); }); it('should have data tables for hydrograph data', () => { @@ -656,70 +351,87 @@ describe('monitoring-location/components/hydrograph module', () => { }); }); - describe('hide elements when showOnlyGraph is set to true', () => { + describe('Tests for rendering once fetching is complete when showOnlyGraph is true', () => { let store; - let resolvedLoadPromise = Promise.resolve(); beforeEach(() => { - jest.spyOn(ivTimeSeriesDataActions, 'retrieveIVTimeSeries').mockReturnValue(function() { - return Promise.resolve({}); - }); - store = configureStore({ - ...TEST_STATE, - ivTimeSeriesData: { - ...TEST_STATE.ivTimeSeriesData, - timeSeries: { - ...TEST_STATE.ivTimeSeriesData.timeSeries, - 'method1:00060:current': { - ...TEST_STATE.ivTimeSeriesData.timeSeries['method1:00060:current'], - startTime: 1514926800000, - endTime: 1514930400000, - points: [{ - dateTime: 1514926800000, - value: 10, - qualifiers: ['P'] - }, { - dateTime: 1514930400000, - value: null, - qualifiers: ['P', 'FLD'] - }] - } - } + hydrographData: { + primaryIVData: TEST_PRIMARY_IV_DATA, + currentTimeRange: TEST_CURRENT_TIME_RANGE, + groundwaterLevels: TEST_GW_LEVELS, + medianStatisticsData: TEST_MEDIAN_DATA }, - ivTimeSeriesState: { - showIVTimeSeries: { - current: true, - compare: true, - median: true - }, - currentIVVariableID: '45807197', - currentIVDateRange: 'P7D', - currentIVMethodID: 'method1', - loadingIVTSKeys: [], - ivGraphBrushOffset: null + hydrographState: { + selectedIVMethodID: '90649', + showMedianData: true }, + hydrographParameters: TEST_HYDROGRAPH_PARAMETERS, + floodData: {}, ui: { - windowWidth: 400, - width: 400 + width: 1000 } + }); + hydrographData.retrieveHydrographData = jest.fn(() => { + return function() { + return Promise.resolve(); + }; + }); + hydrographParameters.retrieveHydrographParameters = jest.fn(() => { + return function() { + return Promise.resolve(); + }; + }); + attachToNode(store, graphNode, { + ...INITIAL_PARAMETERS, + showOnlyGraph: true }); + }); + + it('loading indicator should be hidden', () => { + expect(nodeElem.select('.loading-indicator').size()).toBe(0); + }); - attachToNode(store, graphNode, {siteno: '123456788', showOnlyGraph: true}, resolvedLoadPromise); + it('should render the correct number of svg nodes', () => { + expect(selectAll('svg').size()).toBe(2); + }); + + it('should have a title div', () => { + const titleDiv = selectAll('.time-series-graph-title'); + expect(titleDiv.size()).toBe(1); + }); + + it('should have a defs node', () => { + expect(selectAll('defs').size()).toBe(1); + expect(selectAll('defs mask').size()).toBe(1); + expect(selectAll('defs pattern').size()).toBe(2); + }); + + it('should render time series data as a line', () => { + // There should be four segments + expect(selectAll('.hydrograph-svg .line-segment').size()).toBe(4); + }); + + it('should render a rectangle for masked data', () => { + expect(selectAll('.hydrograph-svg g.iv-mask-group').size()).toBe(1); + }); + + it('should have a point for the median stat data with a label', () => { + expect(selectAll('#median-points path').size()).toBe(1); + expect(selectAll('#median-points text').size()).toBe(0); }); it('should not have brush element for the hydrograph', () => { expect(selectAll('.brush').size()).toBe(0); }); - it('should not have slider-wrapper element', () => { - expect(selectAll('.slider-wrapper').size()).toBe(0); + it('should not have .cursor-slider-svg element', () => { + expect(selectAll('.cursor-slider-svg').size()).toBe(0); }); it('should not have date control elements', () => { expect(selectAll('#ts-daterange-select-container').size()).toBe(0); expect(selectAll('#ts-customdaterange-select-container').size()).toBe(0); - expect(selectAll('#ts-container-radio-group-and-form-buttons').size()).toBe(0); }); it('should not have method select element', () => { @@ -730,8 +442,9 @@ describe('monitoring-location/components/hydrograph module', () => { expect(selectAll('#select-time-series').size()).toBe(0); }); - it('should not have the data table', () => { - expect(select('#iv-data-table-container').selectAll('table').size()).toBe(0); + it('should not have data tables for hydrograph data', () => { + expect(select('#iv-hydrograph-data-table-container').size()).toBe(0); + expect(select('#gw-hydrograph-data-table-container').size()).toBe(0); }); }); }); \ No newline at end of file diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/legend.test.js b/assets/src/scripts/monitoring-location/components/hydrograph/legend.test.js index 9bc61646a3b12260d890868330a1446bf3eb469d..4ccb8941fe131ebc8924ab2654311c1dfcc3c469 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/legend.test.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/legend.test.js @@ -1,111 +1,22 @@ import {select, selectAll} from 'd3-selection'; -import sinon from 'sinon'; import * as utils from 'ui/utils'; import {configureStore} from 'ml/store'; -import {Actions} from 'ml/store/instantaneous-value-time-series-state'; import {drawTimeSeriesLegend} from './legend'; - +import {TEST_PRIMARY_IV_DATA, TEST_GW_LEVELS} from './mock-hydrograph-state'; describe('monitoring-location/components/hydrograph/legend module', () => { utils.mediaQuery = jest.fn().mockReturnValue(true); - const TEST_DATA = { - ivTimeSeriesData: { - timeSeries: { - '00060:current': { - tsKey: 'current:P7D', - startTime: new Date('2018-03-06T15:45:00.000Z'), - endTime: new Date('2018-03-13T13:45:00.000Z'), - variable: '45807197', - points: [{ - value: 10, - qualifiers: ['P'], - approved: false, - estimated: false - }, { - value: null, - qualifiers: ['P', 'ICE'], - approved: false, - estimated: false - }, { - value: null, - qualifiers: ['P', 'FLD'], - approved: false, - estimated: false - }] - }, - - '00060:compare': { - tsKey: 'compare:P7D', - startTime: new Date('2018-03-06T15:45:00.000Z'), - endTime: new Date('2018-03-06T15:45:00.000Z'), - variable: '45807202', - points: [{ - value: 1, - qualifiers: ['A'], - approved: false, - estimated: false - }, { - value: 2, - qualifiers: ['A'], - approved: false, - estimated: false - }, { - value: 3, - qualifiers: ['E'], - approved: false, - estimated: false - }] - } - }, - variables: { - '45807197': { - variableCode: {value: '00060'}, - variableName: 'Streamflow', - variableDescription: 'Discharge, cubic feet per second', - oid: '45807197' - }, - '45807202': { - variableCode: {value: '00065'}, - variableName: 'Gage height', - oid: '45807202' - } - } - }, - statisticsData: { - median: { - '00060': { - '1': [{ - month_nu: '2', - day_nu: '25', - p50_va: '43', - begin_yr: '1970', - end_yr: '2017', - loc_web_ds: 'This method' - }] - } - } + const TEST_STATE = { + hydrographData: { + primaryIVData: TEST_PRIMARY_IV_DATA, + groundwaterLevels: TEST_GW_LEVELS }, - ivTimeSeriesState: { - currentIVVariableID: '45807197', - currentIVDateRange: 'P7D', - showIVTimeSeries: { - current: true, - compare: true, - median: true - } - }, - floodData: { - floodLevels: { - site_no: '07144100', - action_stage: '20', - flood_stage: '22', - moderate_flood_stage: '25', - major_flood_stage: '26' - } + hydrographState: { + selectedIVMethodID: '90649' } }; @@ -113,7 +24,6 @@ describe('monitoring-location/components/hydrograph/legend module', () => { let graphNode; let store; - let fakeServer; beforeEach(() => { let body = select('body'); @@ -125,32 +35,19 @@ describe('monitoring-location/components/hydrograph/legend module', () => { graphNode = document.getElementById('hydrograph'); - store = configureStore(TEST_DATA); + store = configureStore(TEST_STATE); select(graphNode) .call(drawTimeSeriesLegend, store); - fakeServer = sinon.createFakeServer(); }); afterEach(() => { - fakeServer.restore(); select('#hydrograph').remove(); }); it('Should have 6 legend markers', () => { expect(selectAll('.legend g').size()).toBe(6); - expect(selectAll('.legend g line.median-step').size()).toBe(1); - }); - - it('Should have 4 legend marker after the median time series are removed', () => { - store.dispatch(Actions.setIVTimeSeriesVisibility('median', false)); - return new Promise(resolve => { - window.requestAnimationFrame(() => { - expect(selectAll('.legend g').size()).toBe(4); - resolve(); - }); - }); }); }); }); diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/method-picker.js b/assets/src/scripts/monitoring-location/components/hydrograph/method-picker.js index a962c2ea121e827e0f743d1c68fb39fe82762466..7db0083dedee6624c0ec23f61982007a25b72de3 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/method-picker.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/method-picker.js @@ -45,7 +45,7 @@ export const drawMethodPicker = function(elem, store, initialTimeSeriesId) { .attr('selected', method.methodID === selectedMethodID ? true : null) .node().value = method.methodID; }); - pickerContainer.property('hidden', methods.length <= 1); + pickerContainer.attr('hidden', methods.length <= 1 ? true: null); if (methods.length) { elem.dispatch('change'); } diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/method-picker.test.js b/assets/src/scripts/monitoring-location/components/hydrograph/method-picker.test.js index 38affacb80123707b65ebd6ee5e7a6cc159c8be2..8dbe937ab08b2dfe1736a875b3c7696f06a27602 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/method-picker.test.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/method-picker.test.js @@ -3,61 +3,19 @@ import {select} from 'd3-selection'; import {configureStore} from 'ml/store'; import {drawMethodPicker} from './method-picker'; +import {TEST_PRIMARY_IV_DATA} from './mock-hydrograph-state'; describe('monitoring-location/components/hydrograph/method-picker', () => { describe('drawMethodPicker', () => { - const DATA = [12, 13, 14, 15, 16].map(hour => { - return { - dateTime: new Date(`2018-01-03T${hour}:00:00.000Z`).getTime(), - qualifiers: ['P'], - value: hour - }; - }); - const TEST_STATE = { - ivTimeSeriesData: { - timeSeries: { - '69930:00010:current': { - points: DATA, - tsKey: 'current:P7D', - variable: '00010id', - method: 69930 - }, - '69931:00010:current': { - points: DATA, - tsKey: 'current:P7D', - variable: '00010id', - method: 69931 - } - }, - variables: { - '00010id': { - oid: '00010id', - variableCode: { - value: '00010' - }, - unit: { - unitCode: 'deg C' - } - } - }, - methods: { - 69930: { - methodDescription: 'Description 1', - methodID: 69930 - }, - 69931: { - methodDescription: 'Description 2', - methodID: 69931 - } - } + hydrographData: { + primaryIVData: TEST_PRIMARY_IV_DATA }, - ivTimeSeriesState: { - currentIVVariableID: '00010id' + hydrographState: { + selectedIVMethodID: '90649' } }; - let div; beforeEach(() => { div = select('body').append('div'); @@ -70,14 +28,40 @@ describe('monitoring-location/components/hydrograph/method-picker', () => { it('Creates a picker and sets the currentMethodID', () => { let store = configureStore(TEST_STATE); div.call(drawMethodPicker, store); - return new Promise(resolve => { - window.requestAnimationFrame(() => { - expect(div.select('div').property('hidden')).toEqual(false); - expect(div.select('select').property('value')).toEqual('69930'); - expect(store.getState().ivTimeSeriesState.currentIVMethodID).toEqual(69930); - resolve(); - }); + + expect(div.select('#ts-method-select-container').attr('hidden')).toBeNull(); + expect(div.select('select').property('value')).toEqual('90649'); + }); + + it('selecting a different method updates the store', () => { + let store = configureStore(TEST_STATE); + div.call(drawMethodPicker, store); + + const newOption = div.select('option[value="252055"]'); + newOption.attr('selected', true); + + div.select('select').dispatch('change'); + + expect(store.getState().hydrographState.selectedIVMethodID).toBe('252055'); + }); + + it('Expects if the data has only one method then the picker will be hidden', () => { + let store = configureStore({ + ...TEST_STATE, + hydrographData: { + primaryIVData: { + ...TEST_STATE, + values: { + '90649': { + ...TEST_PRIMARY_IV_DATA.values['90649'] + } + } + } + } }); + div.call(drawMethodPicker, store); + + expect(div.select('#ts-method-select-container').attr('hidden')).toBe('true'); }); }); }); diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/mock-hydrograph-state.js b/assets/src/scripts/monitoring-location/components/hydrograph/mock-hydrograph-state.js new file mode 100644 index 0000000000000000000000000000000000000000..23eb92a266dd0b29813fe351c70aee2a6950ccb9 --- /dev/null +++ b/assets/src/scripts/monitoring-location/components/hydrograph/mock-hydrograph-state.js @@ -0,0 +1,100 @@ +export const TEST_CURRENT_TIME_RANGE = { + start: 1582560900000, + end: 1600620300000 +}; +export const TEST_PRIMARY_IV_DATA = { + parameter: { + parameterCode: '72019', + name: 'Depth to water level', + description: 'Depth to water level, feet', + unit: 'ft' + }, + values: { + '90649': { + points: [ + {value: 24.2, qualifiers: ['A'], dateTime: 1582560900000}, + {value: 24.1, qualifiers: ['A'], dateTime: 1582561800000}, + {value: null, qualifiers: ['A', 'ICE'], dateTime: 1582562700000}, + {value: null, qualifiers: ['A', 'ICE'], dateTime: 1582569900000}, + {value: 25.2, qualifiers: ['E'], dateTime: 1582570800000}, + {value: 25.4, qualifiers: ['E'], dateTime: 1600617600000}, + {value: 25.6, qualifiers: ['E'], dateTime: 1600618500000}, + {value: 26.5, qualifiers: ['P'], dateTime: 1600619400000}, + {value: 25.9, qualifiers: ['P'], dateTime: 1600620300000} + ], + method: { + methodID: '90649', + methodDescription: '90649 method description' + } + }, + '252055': { + points: [], + method: { + methodDescription: 'From multiparameter sonde', + methodID: '252055' + } + } + } +}; + +export const TEST_MEDIAN_DATA = { + '153885': [ + {month_nu: 2, day_nu: 24, p50_va: 16, ts_id: '153885', loc_web_ds: 'Method1', begin_yr: '2011', end_yr: '2020'}, + {month_nu: 2, day_nu: 25, p50_va: 16.2, ts_id: '153885', loc_web_ds: 'Method1', begin_yr: '2011', end_yr: '2020'}, + {month_nu: 2, day_nu: 26, p50_va: 15.9, ts_id: '153885', loc_web_ds: 'Method1', begin_yr: '2011', end_yr: '2020'}, + {month_nu: 2, day_nu: 27, p50_va: 16.3, ts_id: '153885', loc_web_ds: 'Method1', begin_yr: '2011', end_yr: '2020'}, + {month_nu: 2, day_nu: 28, p50_va: 16.4, ts_id: '153885', loc_web_ds: 'Method1', begin_yr: '2011', end_yr: '2020'} + ] + }; + +export const TEST_GW_LEVELS = { + parameter: { + parameterCode: '72019', + name: 'Depth to water level' + }, + values: [ + {value: 27.2, qualifiers: ['P'], dateTime: 1582560900000}, + {value: 26.9, qualifiers: ['A'], dateTime: 1582562700000}, + {value: 26.1, qualifiers: ['A'], dateTime: 1582570800000}, + {value: 26.5, qualifiers: ['R'], dateTime: 1600619400000} + ] +}; + +export const TEST_HYDROGRAPH_PARAMETERS = { + '00060': { + parameterCode: '00060', + name: 'Streamflow, ft3/s', + description: 'Discharge, cubic feet per second', + unit: 'ft3/s', + hasIVData: true + }, + '00010': { + parameterCode: '00010', + name: 'Temperature, water, C', + description: 'Temperature, water, degrees Celsius', + unit: 'deg C', + hasIVData: true + }, + '00010F': { + parameterCode: '00010F', + name: 'Temperature, water, F', + description: 'Temperature, water, degrees Fahrenheit', + unit: 'deg F', + hasIVData: true + }, + '72019': { + parameterCode: '72019', + name: 'Depth to water level, ft below land surface', + description: 'Depth to water level, feet below land surface', + unit: 'ft', + hasIVData: true, + hasGWLevelsData: true + }, + '62610': { + parameterCode: '62610', + name: 'Groundwater level above NGVD 1929, feet', + description: 'Groundwater level above NGVD 1929, feet', + unit: 'ft', + hasGWLevelsData: true + } +}; diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/parameters.js b/assets/src/scripts/monitoring-location/components/hydrograph/parameters.js index 1b3d5c4e9935149b84313970da7a79b538265ebe..9c17118cae332dc1160c8c12bbef2480ce5286ee 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/parameters.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/parameters.js @@ -13,7 +13,7 @@ import {getInputsForRetrieval, getSelectedParameterCode} from 'ml/selectors/hydr import {setSelectedParameterCode} from 'ml/store/hydrograph-state'; import {retrieveHydrographData} from 'ml/store/hydrograph-data'; -import {getAvailableParameterCodes} from './selectors/parameter-data'; +import {getAvailableParameters} from './selectors/parameter-data'; /** @@ -21,9 +21,9 @@ import {getAvailableParameterCodes} from './selectors/parameter-data'; * a row changes the active parameter code. */ export const drawSelectionTable = function(container, store, siteno) { - const parameterCodes = getAvailableParameterCodes(store.getState()); + const parameters = getAvailableParameters(store.getState()); - if (!Object.keys(parameterCodes).length) { + if (!Object.keys(parameters).length) { return; } @@ -51,7 +51,7 @@ export const drawSelectionTable = function(container, store, siteno) { table.append('tbody') .attr('class', 'usa-fieldset') .selectAll('tr') - .data(Object.values(parameterCodes)) + .data(Object.values(parameters)) .enter().append('tr') .attr('id', d => `time-series-select-table-row-${d.parameterCode}`) .attr('ga-on', 'click') diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/parameters.test.js b/assets/src/scripts/monitoring-location/components/hydrograph/parameters.test.js index 9e02fc53a4e1a07c30c09a935b255e103112ff1e..deaf291f28a3255c51b3147a434da14ef2ff8b75 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/parameters.test.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/parameters.test.js @@ -1,317 +1,73 @@ import {scaleLinear} from 'd3-scale'; import {select} from 'd3-selection'; +import sinon from 'sinon'; import {configureStore} from 'ml/store'; +import * as hydrographData from 'ml/store/hydrograph-data'; -import {addSparkLine, plotSeriesSelectTable} from './parameters'; +import {TEST_HYDROGRAPH_PARAMETERS} from './mock-hydrograph-state'; +import {drawSelectionTable} from './parameters'; describe('monitoring-location/components/hydrograph/parameters module', () => { - describe('plotSeriesSelectTable', () => { - let tableDivSelection; - - const data = [12, 13, 14, 15, 16].map(day => { - return { - dateTime: new Date(`2018-01-${day}T00:00:00.000Z`), - qualifiers: ['P'], - value: day - }; - }); - - const availableParameterCodes = [ - { - variableID: '00010ID', - parameterCode: '00010', - description: 'Temperature in C', - selected: true, - timeSeriesCount: 1, - periodOfRecord: { - begin_date: '01-02-2001', - end_date: '10-15-2015' - }, - waterAlert: { - hasWaterAlert: true, - subscriptionParameterCode: '00010', - displayText: '00010 Display Text', - tooltipText: '00010 Tooltip Text' - } - }, - { - variableID: '00010IDF', - parameterCode: '00010F', - description: 'Temperature in F', - selected: false, - timeSeriesCount: 1, - periodOfRecord: { - begin_date: '01-02-2001', - end_date: '10-15-2015' - }, - waterAlert: { - hasWaterAlert: true, - subscriptionParameterCode: '00010', - displayText: '00010F Display Text', - tooltipText: '00010F Tooltip Text' - } - }, - { - variableID: '00067ID', - parameterCode: '00067', - description: 'Ruthenium (VI) Fluoride', - selected: false, - timeSeriesCount: 1, - periodOfRecord: { - begin_date: '04-01-1990', - end_date: '10-15-2006' - }, - waterAlert: { - hasWaterAlert: false, - subscriptionParameterCode: '', - displayText: '00067 Display Text', - tooltipText: '00067 Tooltip Text' - } - }, - { - variableID: '00093ID', - parameterCode: '00093', - description: 'Uranium (V) Oxide', - selected: false, - timeSeriesCount: 1, - periodOfRecord: { - begin_date: '11-25-2001', - end_date: '03-01-2020' - }, - waterAlert: { - hasWaterAlert: false, - subscriptionParameterCode: '', - displayText: '00093 Display Text', - tooltipText: '00093 Tooltip Text' - } - } - ]; - - const lineSegmentsByParmCd = { - '00010': [[{'classes': {approved: false, estimated: false, dataMask: null}, points: data}]], - '00093': [[{'classes': {approved: false, estimated: false, dataMask: null}, points: data}]] - }; - - const timeSeriesScalesByParmCd = { - '00010': {x: scaleLinear(new Date(2018, 0, 12), new Date(2018, 0, 16)), y: scaleLinear(0, 100)}, - '00093': {x: scaleLinear(new Date(2018, 0, 12), new Date(2018, 0, 16)), y: scaleLinear(0, 100)} - }; - - const testArgsWithData = { - siteno: '12345678', - availableParameterCodes: availableParameterCodes, - lineSegmentsByParmCd: lineSegmentsByParmCd, - timeSeriesScalesByParmCd: timeSeriesScalesByParmCd - }; - - const testArgsWithoutData = { - siteno: '12345678', - availableParameterCodes: [], - lineSegmentsByParmCd: {}, - timeSeriesScalesByParmCd: {} - }; - - let store = configureStore(); - beforeEach(() => { - tableDivSelection = select('body').append('div'); - }); - - afterEach(() => { - tableDivSelection.remove(); - }); - - it('creates a row for each parameter in a table', () => { - plotSeriesSelectTable(tableDivSelection, testArgsWithData, store); - expect(tableDivSelection.selectAll('tbody tr').size()).toEqual(4); - }); - - it('creates a the correct number svg sparklines in a table', () => { - plotSeriesSelectTable(tableDivSelection, testArgsWithData, store); - expect(tableDivSelection.selectAll('svg').size()).toEqual(4); - expect(tableDivSelection.selectAll('svg path').size()).toEqual(2); - }); - - it('does not create the table when there are no time series', () => { - plotSeriesSelectTable(tableDivSelection, testArgsWithoutData, store); - expect(tableDivSelection.selectAll('table').size()).toEqual(0); - }); - - it('creates a radio button input for each parameter in the table', () => { - plotSeriesSelectTable(tableDivSelection, testArgsWithData, store); - expect(tableDivSelection.selectAll('input').size()).toEqual(4); - }); - - it('creates a WaterAlert subscribe link each parameter in the table supported by WaterAlert as appropriate', () => { - plotSeriesSelectTable(tableDivSelection, testArgsWithData, store); - expect(tableDivSelection.selectAll('.water-alert-cell').size()).toEqual(4); - expect(tableDivSelection.selectAll('a').size()).toEqual(2); - }); - - it('updates the radio button input checked property for the corresponding selected parameter', () => { - plotSeriesSelectTable(tableDivSelection, testArgsWithData, store); - - let selectedParamRow = tableDivSelection.selectAll('tr').filter('.selected'); - let selectedParamTD = selectedParamRow.select('td'); - let selectedParamRowInput = selectedParamTD.select('input'); - expect(selectedParamRowInput.property('checked')).toBeTruthy(); - }); - }); - - describe('addSparkline', () => { - let svg; - const tsDataSinglePoint = { - scales: { - x: scaleLinear(new Date(2015, 1, 2), new Date(2015, 1, 3)), - y: scaleLinear(0, 100) - }, - seriesLineSegments: [ - { - classes: {approved: false, estimated: false, dataMask: null}, - points: [ - {dateTime: new Date(2015, 1, 2), value: 16} - ] - } - ] - }; - const tsDataSingleLine = { - scales: { - x: scaleLinear(new Date(2015, 1, 2), new Date(2015, 1, 3)), - y: scaleLinear(0, 100) - }, - seriesLineSegments: [ - { - classes: {approved: false, estimated: false, dataMask: null}, - points: [ - {dateTime: new Date(2015, 1, 2), value: 16}, - {dateTime: new Date(2015, 1, 3), value: 17} - ] - } - ] - }; - const tsDataMasked = { - scales: { - x: scaleLinear(new Date(2015, 1, 2), new Date(2015, 1, 3)), - y: scaleLinear(0, 100) - }, - seriesLineSegments: [ - { - classes: {approved: false, estimated: false, dataMask: 'ice'}, - points: [ - {dateTime: new Date(2015, 1, 2), value: null}, - {dateTime: new Date(2015, 1, 3), value: null} - ] - } - ] - }; - - const tsDataMasked2 = { - scales: { - x: scaleLinear(new Date(2015, 1, 2), new Date(2015, 1, 3)), - y: scaleLinear(0, 100) - }, - seriesLineSegments: [ - { - classes: {approved: false, estimated: false, dataMask: 'fld'}, - points: [ - {dateTime: new Date(2015, 1, 2), value: null}, - {dateTime: new Date(2015, 1, 3), value: null} - ] - } - ] - }; - const tsDataMultipleMasks = { - scales: { - x: scaleLinear(new Date(2015, 1, 13), new Date(2015, 1, 18)), - y: scaleLinear(0, 100) - }, - seriesLineSegments: [ - { - classes: {approved: false, estimated: false, dataMask: 'fld'}, - points: [ - {dateTime: new Date(2015, 1, 13), value: null}, - {dateTime: new Date(2015, 1, 14), value: null} - ] - }, - { - classes: {approved: false, estimated: false, dataMask: 'ice'}, - points: [ - {dateTime: new Date(2015, 1, 15), value: null}, - {dateTime: new Date(2015, 1, 16), value: null} - ] - } - ] - }; - const tsDataMixed = { - scales: { - x: scaleLinear(new Date(2015, 1, 13), new Date(2015, 1, 18)), - y: scaleLinear(0, 100) - }, - seriesLineSegments: [ - { - classes: {approved: false, estimated: false, dataMask: null}, - points: [ - {dateTime: new Date(2015, 1, 13), value: 84}, - {dateTime: new Date(2015, 1, 14), value: 91} - ] - }, - { - classes: {approved: false, estimated: false, dataMask: 'ice'}, - points: [ - {dateTime: new Date(2015, 1, 15), value: null}, - {dateTime: new Date(2015, 1, 16), value: null} - ] - }, - { - classes: {approved: false, estimated: false, dataMask: null}, - points: [ - {dateTime: new Date(2015, 1, 17), value: 77}, - {dateTime: new Date(2015, 1, 18), value: 85} - ] - } - ] - }; - - beforeEach(() => { - svg = select('body').append('svg'); - }); - - afterEach(() => { - select('svg').remove(); - }); - - it('adds a point for a single point of data', () => { - addSparkLine(svg, tsDataSinglePoint); - expect(svg.selectAll('circle').size()).toEqual(1); - }); - - it('adds a path for a line', () => { - addSparkLine(svg, tsDataSingleLine); - expect(svg.selectAll('path').size()).toEqual(1); - }); - - it('adds multiline text for masked data if the label has more than one word', () => { - addSparkLine(svg, tsDataMasked); - expect(svg.selectAll('text.sparkline-text').size()).toEqual(1); - expect(svg.selectAll('text.sparkline-text tspan').size()).toEqual(2); - }); - - it('adds a single line of text if mask label is one word', () => { - addSparkLine(svg, tsDataMasked2); - expect(svg.selectAll('text.sparkline-text').size()).toEqual(1); - expect(svg.selectAll('text.sparkline-text tspan').size()).toEqual(0); - }); - - it('handles labels if there is more than one mask', () => { - addSparkLine(svg, tsDataMultipleMasks); - expect(svg.selectAll('text.sparkline-text').size()).toEqual(1); - expect(svg.select('text.sparkline-text').text()).toEqual('Masked'); - }); - - it('adds multiple paths if there are breaks in the data', () => { - addSparkLine(svg, tsDataMixed); - expect(svg.selectAll('path').size()).toEqual(2); - }); - }); -}) -; + const TEST_STATE = { + hydrographParameters: TEST_HYDROGRAPH_PARAMETERS, + hydrographState: { + selectedDateRange: 'P7D', + selectedParameterCode: '72019' + } + }; + + let div; + let fakeServer; + let store; + let retrieveHydrographDataSpy; + + beforeEach(() => { + div = select('body').append('div'); + fakeServer = sinon.createFakeServer(); + retrieveHydrographDataSpy = jest.spyOn(hydrographData, 'retrieveHydrographData'); + }); + + afterEach(() => { + fakeServer.restore(); + div.remove(); + }); + + it('If no parameters defined the element is not rendered', () => { + store = configureStore({ + hydrographParameters: {} + }); + drawSelectionTable(div, store, '11112222'); + expect(div.select('#select-time-series').size()).toBe(0); + }); + + it('Expects the selection table to be rendered with the appropriate rows and selection', () => { + store = configureStore(TEST_STATE); + drawSelectionTable(div, store, '11112222'); + + const container = div.select('#select-time-series'); + expect(container.size()).toBe(1); + expect(container.select('table').size()).toBe(1); + expect(container.selectAll('tbody tr').size()).toBe(5); + + expect(container.select('tbody input:checked').attr('value')).toEqual('72019'); + }); + + it('Expects changing the selection retrieves hydrograph data', () => { + store = configureStore(TEST_STATE); + drawSelectionTable(div, store, '11112222'); + + const rowOne = div.select('tbody tr:first-child'); + rowOne.dispatch('click'); + + expect(store.getState().hydrographState.selectedParameterCode).toEqual('00060'); + expect(retrieveHydrographDataSpy).toHaveBeenCalledWith('11112222', { + parameterCode: '00060', + period: 'P7D', + startTime: null, + endTime: null, + loadCompare: false, + loadMedian: false + }); + }); +}); diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/cursor.js b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/cursor.js index a2876ddfa14aa9c33e92885d80bb987af4d42dc1..3bddf88efffd780cba288994dc10515b9d3058fe 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/cursor.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/cursor.js @@ -14,15 +14,15 @@ const isInTimeRange = function(dateTime, timeRange) { return dateTime >= timeRange.start && dateTime <= timeRange.end; }; +/* + * Returns a selector function which returns the cursor offset. If null then + * set to range of the xScale. + */ export const getCursorOffset = createSelector( getMainXScale('current'), getGraphCursorOffset, (xScale, cursorOffset) => { - // If cursorOffset is false, don't show it - if (cursorOffset === false) { - return null; - // If cursorOffset is otherwise unset, default to the last offset - } else if (!cursorOffset) { + if (!cursorOffset) { const domain = xScale.domain(); return domain[1] - domain[0]; } else { @@ -32,9 +32,9 @@ export const getCursorOffset = createSelector( ); /** - * Returns a selector that, for a given tsKey: + * Returns a selector that, for a given timeRangeKind * Returns the time corresponding to the current cursor offset. - * @param {String} tsKey + * @param {String} timeRangeKind - 'current' or 'prioryear' * @return {Date} */ export const getCursorTime = memoize(timeRangeKind => createSelector( @@ -47,62 +47,54 @@ export const getCursorTime = memoize(timeRangeKind => createSelector( /* * Returns a Redux selector function that returns the time series data point nearest - * the tooltip focus time for the current time series with the current variable and current method - * @param {Object} state - Redux store - * @param String} tsKey - Time series key - * @return {Object} + * the tooltip focus time for the dataKind and timeRangeKind and the selected method + * @param {String} dataKind - 'primary' or 'compare' + * @param {String} timeRangeKind - 'current' or 'prioryear' + * @return {Object} or null */ -export const getIVDataCursorPoints = memoize((dataRange, timeRangeKind) => createSelector( - getIVDataPoints(dataRange), +export const getIVDataCursorPoint = memoize((dataKind, timeRangeKind) => createSelector( + getIVDataPoints(dataKind), getSelectedIVMethodID, getCursorTime(timeRangeKind), - isVisible(dataRange), + isVisible(dataKind), getGraphTimeRange('MAIN', timeRangeKind), - (ivData, currentMethodID, cursorTime, isVisible, timeRange) => { - if (!ivData || !cursorTime || !isVisible || !timeRange) { - return {}; + (ivData, selectedMethodID, cursorTime, isVisible, timeRange) => { + if (!ivData || !cursorTime || !isVisible || !timeRange || !selectedMethodID || !(selectedMethodID in ivData)) { + return null; + } + const visiblePoints = ivData[selectedMethodID].filter(point => isInTimeRange(point.dateTime, timeRange)); + if (!visiblePoints.length) { + return null; } - return Object.keys(ivData).reduce((byMethodID, methodID) => { - const isCurrentMethod = currentMethodID ? currentMethodID === methodID : true; - if (ivData[methodID].length && isCurrentMethod) { - const visiblePoints = ivData[methodID].filter(point => isInTimeRange(point.dateTime, timeRange)); - if (visiblePoints.length) { - const datum = getNearestTime(visiblePoints, cursorTime); - byMethodID[methodID] = { - ...datum, - dataRange: dataRange - }; - } - } - return byMethodID; - }, {}); + const datum = getNearestTime(visiblePoints, cursorTime); + return { + ...datum, + dataKind: dataKind + }; })); /* * Returns a function that returns the time series data point nearest the - * tooltip focus time for the given time series key. Only returns those points - * where the y-value is finite; no use in making a point if y is Infinity. + * tooltip focus time for the given dataKind and timeRangeKind. Returns null if y value is infinite; + * no use in making a point if y is Infinity. * - * @param {Object} state - Redux store - * @param {String} tsKey - Time series key - * @return {Function} which returns an {Array of Object} tooltipPoints - Each - * object has x and y properties. + * @param {String} dataKind - 'primary' or 'compare' + * @param {String} timeRangeKind - 'current' or 'prioryear' + * @return {Function} which returns an {Object} with x and y properties */ -export const getIVDataTooltipPoints = memoize((dataRange, timeRange) => createSelector( - getMainXScale(timeRange), +export const getIVDataTooltipPoint = memoize((dataKind, timeRangeKind) => createSelector( + getMainXScale(timeRangeKind), getMainYScale, - getIVDataCursorPoints(dataRange, timeRange), - (xScale, yScale, cursorPoints) => { - return Object.keys(cursorPoints).reduce((tooltipPoints, tsID) => { - const cursorPoint = cursorPoints[tsID]; - if (isFinite(yScale(cursorPoint.value))) { - tooltipPoints.push({ - x: xScale(cursorPoint.dateTime), - y: yScale(cursorPoint.value) - }); - } - return tooltipPoints; - }, []); + getIVDataCursorPoint(dataKind, timeRangeKind), + (xScale, yScale, cursorPoint) => { + if (cursorPoint && isFinite(yScale(cursorPoint.value))) { + return { + x: xScale(cursorPoint.dateTime), + y: yScale(cursorPoint.value) + }; + } else { + return null; + } } )); diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/cursor.test.js b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/cursor.test.js index 35ac99278e33f4b731f49014059ac7da6099cb45..5c0f770ffc14548710ef4a526c53f73f0f589520 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/cursor.test.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/cursor.test.js @@ -1,9 +1,6 @@ import * as utils from 'ui/utils'; -import {configureStore} from 'ml/store'; -import {Actions} from 'ml/store/instantaneous-value-time-series-state'; - -import {getTsCursorPoints, getCursorOffset, getTooltipPoints, getGroundwaterLevelCursorPoint, +import {getCursorOffset, getCursorTime, getIVDataCursorPoint, getIVDataTooltipPoint, getGroundwaterLevelCursorPoint, getGroundwaterLevelTooltipPoint} from './cursor'; let DATA = [12, 13, 14, 15, 16].map(hour => { @@ -24,520 +21,227 @@ DATA = DATA.concat([ qualifiers: ['Mnt', 'P'], value: null } - ]); -const TEST_STATE_THREE_VARS = { - ivTimeSeriesData: { - queryInfo: { - 'current:P7D': { - notes: { - 'filter:timeRange': { - mode: 'RANGE', - interval: { - start: 1522346400000, - end: 1522349100000 - } - } - } - }, - 'compare:P7D': { - notes: { - 'filter:timeRange': { - mode: 'RANGE', - interval: { - start: 1522346400000, - end: 1522349100000 - } - } - } - } - }, - methods: { - 69927: { - methodDescription: '', - methodID: 69927 - }, - 69928: { - methodDescription: '', - methodID: 69928 - }, - 69929: { - methodDescription: '', - methodID: 69929 - }, - 69930: { - methodDescription: '', - methodID: 69930 - } - }, - timeSeries: { - '69928:current:P7D': { - tsKey: 'current:P7D', - startTime: 1520351100000, - endTime: 1520955900000, - variable: '45807197', - method: 69928, - points: [{ - value: 10, - qualifiers: ['P'], - dateTime: 1522346400000 - }, { - value: null, - qualifiers: ['P', 'ICE'], - dateTime: 1522347300000 - }, { - value: null, - qualifiers: ['P', 'FLD'], - dateTime: 1522348200000 - }] - }, - '69927:current:P7D': { - tsKey: 'current:P7D', - startTime: 1520351100000, - endTime: 1520955900000, - variable: '45807196', - method: 69927, - points: [{ - value: 10, - qualifiers: ['P'], - dateTime: 1522346400000 - }, { - value: 11, - qualifiers: ['P', 'ICE'], - dateTime: 1522347300000 - }, { - value: 12, - qualifiers: ['P', 'FLD'], - dateTime: 1522348200000 - }] - }, - '69929:current:P7D': { - tsKey: 'current:P7D', - startTime: 1520351100000, - endTime: 1520955900000, - variable: '45807196', - method: 69929, - points: [{ - value: 1, - qualifiers: ['P'], - dateTime: 1522346400000 - }, { - value: 2, - qualifiers: ['P'], - dateTime: 1522347300000 - }, { - value: 3, - qualifiers: ['P'], - dateTime: 1522348200000 - }] - }, - '69930:current:P7D': { - tsKey: 'current:P7D', - startTime: 1488815100000, - endTime: 1489419900000, - method: 69930, - variable: '45807140', - points: [{ - value: 0, - qualifiers: ['P'], - dateTime: 1522346400000 - }, { - value: 0.01, - qualifiers: ['P'], - dateTime: 1522347300000 - }, { - value: 0.02, - qualifiers: ['P'], - dateTime: 1522348200000 - }, { - value: 0.03, - qualifiers: ['P'], - dateTime: 1522349100000 - }] - } - }, - timeSeriesCollections: { - 'coll1': { - variable: 45807197, - timeSeries: ['00060'] - } - }, - requests: { - 'current:P7D': { - timeSeriesCollections: ['coll1'] - } - }, - variables: { - '45807197': { - variableCode: {value: '00060'}, - variableName: 'Streamflow', - variableDescription: 'Discharge, cubic feet per second', - oid: '45807197' - }, - '45807196': { - variableCode: {value: '00010'}, - variableName: 'Gage Height', - variableDescription: 'Gage Height in feet', - oid: '45807196' - }, - '45807140': { - variableCode: {value: '00045'}, - variableName: 'Precipitation', - variableDescription: 'Precipitation in inches', - oid: '45807140' - } - } - }, - statisticsData: {}, - ivTimeSeriesState: { - showIVTimeSeries: { - current: true, - compare: false, - median: false - }, - currentIVVariableID: '45807197', - currentIVMethodID: 69928, - currentIVDateRange: 'P7D', - audiblePlayId: null +const TEST_IV_DATA = { + parameter: { + parameterCode: '00060' }, - ui: { - windowWidth: 1024, - width: 800 - }, - discreteData: { - groundwaterLevels: { - '45807140': { - variable: { - variableCode: {value: '00045'}, - variableName: 'Precipitation', - variableDescription: 'Precipitation in inches', - oid: '45807140' - }, - values: [{ - value: '10', - qualifiers: [], - dateTime: 1522346400000 - }, { - value: '20', - qualifiers: [], - dateTime: 1522347300000 - }, { - value: '30', - qualifiers: [], - dateTime: 1522348200000 - }] + values: { + '90649': { + points: DATA, + method: { + methodID: '90649' } } } }; -const TEST_STATE_ONE_VAR = { - ivTimeSeriesData: { - timeSeries: { - '69928:current:P7D': { - points: DATA, - tsKey: 'current:P7D', - variable: '00060id', - methodID: 69928 - }, - '69928:compare:P7D': { - points: DATA, - tsKey: 'compare:P7D', - variable: '00060id', - methodID: 69928 - } - }, - timeSeriesCollections: { - 'current:P7D': { - variable: '00060id', - timeSeries: ['00060:current'] - }, - 'compare:P7D': { - variable: '00060id', - timeSeries: ['00060:compare'] - } - }, - methods: { - 69928: { - methodDescription: '', - methodID: 69928 - }, - 69929: { - methodDescription: '', - methodID: 69929 - }, - 69930: { - methodDescription: '', - methodID: 69930 - } - }, - variables: { - '00060id': { - oid: '00060id', - variableCode: { - value: '00060' - }, - unit: { - unitCode: 'ft3/s' - } - } - }, - requests: { - 'current:P7D': { - timeSeriesCollections: ['current'] - }, - 'compare:P7D': { - timeSeriesCollections: ['compare'] - } - }, - queryInfo: { - 'current:P7D': { - notes: { - 'filter:timeRange': { - mode: 'RANGE', - interval: { - start: 1514980800000, - end: 1514995200000 - } - } - } - }, - 'compare:P7D': { - notes: { - 'filter:timeRange': { - mode: 'RANGE', - interval: { - start: 1514980800000, - end: 1514995200000 - } - } - } - } - }, - qualifiers: { - 'P': { - qualifierCode: 'P', - qualifierDescription: 'Provisional DATA subject to revision.', - qualifierID: 0, - network: 'NWIS', - vocabulary: 'uv_rmk_cd' - }, - 'Fld': { - qualifierCode: 'Fld', - qualifierDescription: 'Flood', - qualifierId: 0, - network: 'NWIS', - vocabulary: 'uv_rmk_cd' - }, - 'Mnt': { - qualifierCode: 'Mnt', - qualifierDescription: 'Maintenance', - qualifierId: 0, - network: 'NWIS', - vocabulary: 'uv_rmk_cd' - } - } - }, - statisticsData: {}, - ivTimeSeriesState: { - showIVTimeSeries: { - current: true, - compare: true - }, - currentIVVariableID: '00060id', - currentIVMethodID: 69928, - currentIVDateRange: 'P7D', - ivGraphCursorOffset: null - }, - ui: { - windowWidth: 1024, - width: 800 +const TEST_GW_LEVELS = { + parameter: { + parameterCode: '00060' }, - discreteData: {} + values: [{ + value: 20.1, + qualifiers: ['A'], + dateTime: new Date('2018-01-03T13:00:00.000Z').getTime() + }, { + value: 18.3, + qualifiers: ['A'], + dateTime: new Date('2018-01-03T15:00:00.000Z').getTime() + }] }; -describe('monitoring-location/components/hydrograph/cursor module', () => { +const TEST_TIME_RANGE = { + start: 1514980800000, + end: 1515002400000 +}; + + +describe('monitoring-location/components/hydrograph/selectors/cursor module', () => { utils.mediaQuery = jest.fn().mockReturnValue(true); + const TEST_STATE = { + hydrographData: { + currentTimeRange: TEST_TIME_RANGE, + primaryIVData: TEST_IV_DATA + }, + hydrographState: { + selectedIVMethodID: '90649' + }, + ui: { + windowWidth: 1400, + width: 990 + } + }; - describe('getTsCursorPoints', () => { - it('Should return last time with non-masked value if the cursor offset is null', function() { - expect(getTsCursorPoints('compare')(TEST_STATE_ONE_VAR)).toEqual({ - '69928:compare:P7D': { - dateTime: 1514995200000, - qualifiers: ['P'], - value: 16, - tsKey: 'compare' - } - }); - expect(getTsCursorPoints('current')(TEST_STATE_ONE_VAR)).toEqual({ - '69928:current:P7D': { - dateTime: 1514995200000, - qualifiers: ['P'], - value: 16, - tsKey: 'current' + describe('getCursorOffset', () => { + it('returns null when false', () => { + const cursorRange = DATA[DATA.length - 1].dateTime - DATA[0].dateTime; + expect(getCursorOffset({ + ...TEST_STATE, + hydrographState: { + ...TEST_STATE.hydrographState, + graphCursorOffset: false } - }); + })).toBe(cursorRange); }); - it('Should return the nearest datum for the selected time series', function() { - let state = { - ...TEST_STATE_ONE_VAR, - ivTimeSeriesState: { - ...TEST_STATE_ONE_VAR.ivTimeSeriesState, - ivGraphCursorOffset: 149 * 60 * 1000 + it('returns the cursor offset if set', () => { + expect(getCursorOffset({ + ...TEST_STATE, + hydrographState: { + ...TEST_STATE.hydrographState, + graphCursorOffset: 25000000 } - }; - - expect(getTsCursorPoints('current')(state)['69928:current:P7D'].value).toEqual(14); - expect(getTsCursorPoints('compare')(state)['69928:compare:P7D'].value).toEqual(14); + })).toEqual(25000000); }); + }); - it('Selects the nearest point for the current variable streamflow', () => { - const newState = { - ...TEST_STATE_THREE_VARS, - ivTimeSeriesState: { - ...TEST_STATE_THREE_VARS.ivTimeSeriesState, - currentIVVariableID: '45807196', - currentIVMethodID: 69929, - ivGraphCursorOffset: 16 * 60 * 1000 - } - }; - expect(getTsCursorPoints('current')(newState)).toEqual({ - '69929:current:P7D': { - value: 2, - qualifiers: ['P'], - dateTime: 1522347300000, - tsKey: 'current' - } - }); + describe('getCursorTime', () => { + it('returns last date if graphCursorOffset is not set', () => { + expect(getCursorTime('current')(TEST_STATE)).toEqual(new Date(DATA[DATA.length - 1].dateTime)); }); - it('Selects the nearest point for current variable precipitation', () => { - const newState = { - ...TEST_STATE_THREE_VARS, - ivTimeSeriesState: { - ...TEST_STATE_THREE_VARS.ivTimeSeriesState, - currentIVVariableID: '45807140', - currentIVMethodID: 69930, - ivGraphCursorOffset: 29 * 60 * 1000 + it('Returns the epoch time where the cursor is', () => { + expect(getCursorTime('current')({ + ...TEST_STATE, + hydrographState: { + ...TEST_STATE.hydrographState, + graphCursorOffset: 25000000 } - }; - - expect(getTsCursorPoints('current')(newState)).toEqual({ - '69930:current:P7D': { - value: 0.03, - qualifiers: ['P'], - dateTime: 1522348200000, - tsKey: 'current' - } - }); + })).toEqual(new Date(DATA[0].dateTime + 25000000)); }); }); - describe('getCursorOffset', () => { - let store; - beforeEach(() => { - store = configureStore(TEST_STATE_ONE_VAR); + describe('getIVDataCursorPoint', () => { + it('Return an null if no IV data', () => { + expect(getIVDataCursorPoint('compare', 'prioryear')(TEST_STATE)).toBeNull(); }); - it('returns null when false', () => { - store.dispatch(Actions.setIVGraphCursorOffset(false)); - expect(getCursorOffset(store.getState())).toBe(null); + it('Return the point nearest to the cursor when no brush offset', () => { + expect(getIVDataCursorPoint('primary', 'current')({ + ...TEST_STATE, + hydrographState: { + ...TEST_STATE.hydrographState, + graphCursorOffset: 7200000 + } + })).toEqual({ + dateTime: 1514988000000, + value: 14, + approvalQualifier: undefined, + maskedQualifier: undefined, + class: 'provisional', + isMasked: false, + label: 'Provisional', + dataKind: 'primary' + }); }); - it('returns last point when null', () => { - store.dispatch(Actions.setIVGraphCursorOffset(null)); - const cursorRange = DATA[4].dateTime - DATA[0].dateTime; - expect(getCursorOffset(store.getState())).toBe(cursorRange); + it('Return the nearest point to the cursor when there is a brush offset', () => { + expect(getIVDataCursorPoint('primary', 'current')({ + ...TEST_STATE, + hydrographState: { + ...TEST_STATE.hydrographState, + graphCursorOffset: 1000, + graphBrushOffset: { + start: 10000000, + end: 0 + } + } + })).toEqual({ + dateTime: 1514991600000, + value: 15, + approvalQualifier: undefined, + maskedQualifier: undefined, + class: 'provisional', + isMasked: false, + label: 'Provisional', + dataKind: 'primary' + }); }); }); - describe('getTooltipPoints', () => { - const id = (val) => val; - - it('should return the requested time series focus time', () => { - expect(getTooltipPoints('current').resultFunc(id, id, { - '00060:current': { - dateTime: '1date', - value: 1 - }, - '00060:compare': { - dateTime: '2date', - value: 2 - } - })).toEqual([{ - x: '1date', - y: 1 - }, { - x: '2date', - y: 2 - }]); + describe('getIVDataTooltipPoint', () => { + it('Returns null if no iv data', () => { + expect(getIVDataTooltipPoint('compare', 'prioryear')(TEST_STATE)).toBeNull(); }); - it('should exclude values that are infinite', () => { - expect(getTooltipPoints('current').resultFunc(id, id, { - '00060:current': { - dateTime: '1date', - value: Infinity - }, - '00060:compare': { - dateTime: '2date', - value: 2 + it('Returns the expected point if cursor offset is set', () => { + const point = getIVDataTooltipPoint('primary', 'current')({ + ...TEST_STATE, + hydrographState: { + ...TEST_STATE.hydrographState, + graphCursorOffset: 7200000 } - })).toEqual([{ - x: '2date', - y: 2 - }]); + }); + expect(point.x).toBeDefined(); + expect(point.y).toBeDefined(); }); }); describe('getGroundwaterLevelCursorPoint', () => { - it('Return null if no groundwater levels are defined', () => { - const testState = { - ...TEST_STATE_THREE_VARS, - ivTimeSeriesState: { - ...TEST_STATE_THREE_VARS.ivTimeSeriesState, - currentIVVariableID: '45807140', - ivGraphCursorOffset: 16 * 60 * 1000 - }, - discreteData: {} - }; - expect(getGroundwaterLevelCursorPoint(testState)).toBeNull(); + it('Return null if no groundwater levels data', () => { + expect(getGroundwaterLevelCursorPoint(TEST_STATE)).toBeNull(); }); - it('Return the expected nearest point', () => { - const testState = { - ...TEST_STATE_THREE_VARS, - ivTimeSeriesState: { - ...TEST_STATE_THREE_VARS.ivTimeSeriesState, - currentIVVariableID: '45807140', - ivGraphCursorOffset: 16 * 60 * 1000 + it('Return nearest point when brush offset is null', () => { + expect(getGroundwaterLevelCursorPoint({ + ...TEST_STATE, + hydrographData: { + ...TEST_STATE.hydrographData, + groundwaterLevels: TEST_GW_LEVELS + }, + hydrographState: { + ...TEST_STATE.hydrographState, + graphCursorOffset: 7200000 } - }; + })).toEqual({ + value: 20.1, + dateTime: 1514984400000 + }); + }); - expect(getGroundwaterLevelCursorPoint(testState)).toEqual({ - value: 20, - qualifiers: [], - dateTime: 1522347300000 + it('Return nearest point brush offset is not null', () => { + expect(getGroundwaterLevelCursorPoint({ + ...TEST_STATE, + hydrographData: { + ...TEST_STATE.hydrographData, + groundwaterLevels: TEST_GW_LEVELS + }, + hydrographState: { + ...TEST_STATE.hydrographState, + graphCursorOffset: 1000, + graphBrushOffset: { + start: 10000000, + end: 0 + } + } + })).toEqual({ + value: 18.3, + dateTime: 1514991600000 }); }); }); describe('getGroundwaterLevelTooltipPoint', () => { - const id = (val) => val; + it('Return null when no ground water levels', () => { + expect(getGroundwaterLevelTooltipPoint(TEST_STATE)).toBeNull(); + }); - it('should return the requested time series focus time', () => { - expect(getGroundwaterLevelTooltipPoint.resultFunc({ - dateTime: '1date', - value: 1 - }, id, id)).toEqual({ - x: '1date', - y: 1 - }, { - x: '2date', - y: 2 + it('Returns a point when ground water levels are defined', () => { + const point = getGroundwaterLevelTooltipPoint({ + ...TEST_STATE, + hydrographData: { + ...TEST_STATE.hydrographData, + groundwaterLevels: TEST_GW_LEVELS + }, + hydrographState: { + ...TEST_STATE.hydrographState, + graphCursorOffset: 7200000 + } }); + expect(point.x).toBeDefined(); + expect(point.y).toBeDefined(); }); }); }); diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/discrete-data.js b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/discrete-data.js index 04283bb32a0c9eb61563d201c5cbc34966c7fbb9..48482a191a54f0e51299b78f8208282e47d81203 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/discrete-data.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/discrete-data.js @@ -28,6 +28,22 @@ export const getGroundwaterLevelPoints = createSelector( } ); +/* +* When given an approval code, will return the text equivalent +* @param {String} approvalCode - Usually a letter such as 'A' +* @return {String} - an easy to understand text version of an approval code +*/ +const approvalCodeText = function(approvalCode) { + const approvalText = { + P: 'Provisional', + A: 'Approved', + R: 'Revised', + default: `unknown code: ${approvalCode}` + }; + + return approvalText[approvalCode] || approvalText.default; +}; + /* * Selector function which returns a function that returns an array of gw data appropriate * for use in a table. @@ -49,7 +65,8 @@ export const getGroundwaterLevelsTableData = createSelector( dateTime: DateTime.fromMillis(point.dateTime, {zone: config.locationTimeZone}).toISO({ suppressMilliseconds: true, suppressSeconds: true - }) + }), + approvals: approvalCodeText(point.qualifiers[0]) }; }); } diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/discrete-data.test.js b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/discrete-data.test.js index bfdbdb239622fe39ba76c485b119431b214b72e0..21cb584a1cf79a8738fa717ccb65fee1c2d737b7 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/discrete-data.test.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/discrete-data.test.js @@ -1,153 +1,104 @@ -import {getVisibleGroundwaterLevelPoints, getVisibleGroundwaterLevelsTableData, +import config from 'ui/config'; + +import {getGroundwaterLevelPoints, getGroundwaterLevelsTableData, anyVisibleGroundwaterLevels} from './discrete-data'; describe('monitoring-location/components/hydrograph/selectors/discrete-data', () => { + config.locationTimeZone = 'America/Chicago'; + const TEST_GW_LEVELS = { + parameter: { + parameterCode: '72019', + name: 'Depth to water level' + }, + values: [ + {value: 27.2, qualifiers: ['P'], dateTime: 1584648660000}, + {value: 26.9, qualifiers: ['A'], dateTime: 1589388420000}, + {value: 26.1, qualifiers: ['A'], dateTime: 1595522700000}, + {value: 26.5, qualifiers: ['R'], dateTime: 1598303040000} + ] + }; const TEST_STATE = { - ianaTimeZone: 'America/Chicago', - ivTimeSeriesData: { - queryInfo: { - 'current:P7D': { - notes: { - requestDT: 1490936400000, - 'filter:timeRange': { - mode: 'PERIOD', - periodDays: 7, - modifiedSince: null - } - } - } - }, - variables: { - '45807042': { - variableCode: { - 'value': '72019' - }, - variableName: 'Depth to water level' - }, - '45807041': { - variableCode: { - 'value': '00060' - }, - variableName: 'Streamflow' - } - } - }, - ivTimeSeriesState: { - currentIVDateRange: 'P7D', - currentIVVariableID: '45807042' - }, - discreteData: { - groundwaterLevels: { - '45807042': { - variable: { - variableCode: { - value: '72019' - }, - oid: '45807042' - }, - values: [ - {value: '12.0', dateTime: 1489672800000}, - {value: '13.0', dateTime: 1490536800000}, - {value: '14.5', dateTime: 1490882400000}, - {value: '14.0', dateTime: 1491055200000} - ] - } - } - } - }; - - describe('getVisibleGroundwaterLevelPoints', () => { + hydrographData: { + groundwaterLevels: TEST_GW_LEVELS + } + }; + describe('getGroundwaterLevelPoints', () => { it('Return empty array if no groundwater levels are defined', () => { - const testData = { - ...TEST_STATE, - discreteData: { - groundwaterLevels: null - } - }; - expect(getVisibleGroundwaterLevelPoints(testData)).toHaveLength(0); - }); - - it('Return an empty array if the current variable does not have ground water data', () => { - const testData = { - ...TEST_STATE, - ivTimeSeriesState: { - ...TEST_STATE.ivTimeSeriesState, - currentIVVariableID: '45807041' - } - }; - expect(getVisibleGroundwaterLevelPoints(testData)).toHaveLength(0); + expect(getGroundwaterLevelPoints({ + hydrographData: {} + })).toHaveLength(0); }); - it('Return the ground water levels that are in the 7 day period', () => { - const result = getVisibleGroundwaterLevelPoints(TEST_STATE); - - expect(result).toHaveLength(2); - expect(result[0]).toEqual({ - value: 13.0, - dateTime: 1490536800000 + it('Return the ground water points when groundwater levels are defined', () => { + const points = getGroundwaterLevelPoints(TEST_STATE); + expect(points.length).toBe(4); + expect(points[0]).toEqual({ + value: 27.2, + dateTime: 1584648660000 + }); + expect(points[1]).toEqual({ + value: 26.9, + dateTime: 1589388420000 }); - expect(result[1]).toEqual({ - value: 14.5, - dateTime: 1490882400000 + expect(points[2]).toEqual({ + value: 26.1, + dateTime: 1595522700000 + }); + expect(points[3]).toEqual({ + value: 26.5, + dateTime: 1598303040000 }); }); }); - describe('getVisibleGroundwaterLevelsTableData', () => { - - it('Return empty array if no groundwater levels are defined', () => { - const testData = { - ...TEST_STATE, - discreteData: { - groundwaterLevels: null - } - }; - expect(getVisibleGroundwaterLevelsTableData(testData)).toHaveLength(0); - }); - - it('Return an empty array if the current variable does not have ground water data', () => { - const testData = { - ...TEST_STATE, - ivTimeSeriesState: { - ...TEST_STATE.ivTimeSeriesState, - currentIVVariableID: '45807041' - } - }; - expect(getVisibleGroundwaterLevelsTableData(testData)).toHaveLength(0); + describe('getGroundwaterLevelsTableData', () => { + it('Returns an empty array if no groundwater levels', () => { + expect(getGroundwaterLevelsTableData({ + hydrographData: {} + })).toHaveLength(0); }); - it('Return the ground water levels that are in the 7 day period', () => { - const result = getVisibleGroundwaterLevelsTableData(TEST_STATE); - - expect(result).toHaveLength(2); - expect(result[0]).toEqual({ + it('Returns the array of groundwater levels table data', () => { + const tableData = getGroundwaterLevelsTableData(TEST_STATE); + expect(tableData).toHaveLength(4); + expect(tableData[0]).toEqual({ + parameterName: 'Depth to water level', + result: '27.2', + dateTime: '2020-03-19T15:11-05:00', + approvals: 'Provisional' + }); + expect(tableData[1]).toEqual({ + parameterName: 'Depth to water level', + result: '26.9', + dateTime: '2020-05-13T11:47-05:00', + approvals: 'Approved' + }); + expect(tableData[2]).toEqual({ parameterName: 'Depth to water level', - result: '13', - dateTime: '2017-03-26T09:00-05:00' + result: '26.1', + dateTime: '2020-07-23T11:45-05:00', + approvals: 'Approved' }); - expect(result[1]).toEqual({ + expect(tableData[3]).toEqual({ parameterName: 'Depth to water level', - result: '14.5', - dateTime: '2017-03-30T09:00-05:00' + result: '26.5', + dateTime: '2020-08-24T16:04-05:00', + approvals: 'Revised' }); }); }); describe('anyVisibleGroundwaterLevels', () => { it('Return false if no visible ground water levels', () => { - const testData = { - ...TEST_STATE, - discreteData: { - groundwaterLevels: null - } - }; - expect(anyVisibleGroundwaterLevels(testData)).toBe(false); + expect(anyVisibleGroundwaterLevels({ + hydrographData: {} + })).toBe(false); }); it('Return true if visible ground water levels', () => { expect(anyVisibleGroundwaterLevels(TEST_STATE)).toBe(true); }); }); -}); \ No newline at end of file +}); diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/domain.js b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/domain.js index ccba54917de236eb257997083bae9e19fca96b79..9c512e97723d79229f2808599acd822e92f2eb85 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/domain.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/domain.js @@ -215,8 +215,8 @@ export const getPrimaryValueRange = createSelector( export const getYTickDetails = createSelector( getPrimaryValueRange, getPrimaryParameter, - (yDomain, parameterCode) => { - const isSymlog = SYMLOG_PARMS.indexOf(parameterCode) > -1; + (yDomain, parameter) => { + const isSymlog = SYMLOG_PARMS.indexOf(parameter.parameterCode) > -1; let tickValues = ticks(yDomain[0], yDomain[1], Y_TICK_COUNT); diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/domain.test.js b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/domain.test.js index c106561778a2ad5609a450e20b59f846844b9aaa..aa30dce29b5165a5ce6d24cf1a02edcbbf7aef31 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/domain.test.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/domain.test.js @@ -1,6 +1,5 @@ import { extendDomain, - getYDomain, getYTickDetails, getFullArrayOfAdditionalTickMarks, getLowestAbsoluteValueOfTickValues, @@ -58,64 +57,9 @@ describe('monitoring-location/components/hydrograph/selectors/domain module', () }); }); - describe('getYDomain', () => { - function pts(arr) { - return arr.map(val => { - return { - value: val - }; - }); - } - - it('is inclusive to all points with symlog', () => { - const domain = getYDomain( - [pts([1, 2, 3]), pts([5, 6, 7]), pts([-10, 2])], - {variableCode: {value: '00060'}} - ); - expect(domain[0]).toBeLessThanOrEqual(-10); - expect(domain[1]).toBeGreaterThanOrEqual(7); - }); - - it('is inclusive to all points with linear', () => { - const domain = getYDomain( - [pts([1, 2, 3]), pts([5, 6, 7]), pts([-10, 2])], - {variableCode: {value: '00065'}} - ); - expect(domain[0]).toBeLessThanOrEqual(-10); - expect(domain[1]).toBeGreaterThanOrEqual(7); - }); - - it('ignores non-finite values', () => { - const domain = getYDomain( - [pts([-Infinity, NaN, 1, 2, 3, Infinity])], - {variableCode: {value: '00065'}} - ); - const padding = (3 - 1) * .2; - expect(domain).toEqual([1 - padding, 3 + padding]); - }); - - it('handles single point values', () => { - const domain = getYDomain( - [pts([100])] - ); - expect(domain[0]).toBeLessThanOrEqual(50); - expect(domain[1]).toBeGreaterThanOrEqual(150); - }); - - it('handles single point values of 0', () => { - const domainSymlog = getYDomain([pts([0, 0, 0])], {variableCode: {value: '00060'}}); - expect(domainSymlog[0]).toBeLessThanOrEqual(0); - expect(domainSymlog[1]).toBeGreaterThanOrEqual(1); - - const domainLinear = getYDomain([pts([0, 0, 0])], {variableCode: {value: '00045'}}); - expect(domainLinear[0]).toBeLessThanOrEqual(0); - expect(domainLinear[1]).toBeGreaterThanOrEqual(1); - }); - }); - describe('getYTickDetails', () => { it('returns ticks and a formatting function', () => { - const tickDetails = getYTickDetails.resultFunc([0, 1]); + const tickDetails = getYTickDetails.resultFunc([0, 1], {parameterCode: '00065'}); expect(tickDetails.tickValues).toEqual(expect.any(Array)); expect(tickDetails.tickFormat).toEqual(expect.any(Function)); expect(tickDetails.tickFormat(1)).toEqual(expect.any(String)); diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/iv-data.js b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/iv-data.js index 0a68caa3f82b670a425c2836892352d348de7d41..ab6cb139024bf151b6f4fc3cf1d9e2f30000d9ba 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/iv-data.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/iv-data.js @@ -38,7 +38,7 @@ export const HASH_ID = { }; // Lines will be split if the difference exceeds 72 minutes. -export const SEVENTY_TWO_MINUTES = 60 * 1000 * 72; +const SEVENTY_TWO_MINUTES = 60 * 1000 * 72; const PARM_CODES_TO_ACCUMULATE = ['00045']; @@ -71,6 +71,8 @@ const transformToCumulative = function(points) { * @prop {Number} value * @prop {Number} dateTime - in epoch milliseconds * @prop {Boolean} isMasked + * @prop {String} maskedQualifier + * @prop {String} approvalQualifier * @prop {String} label - human readable label * @prop {String} class - can be used to style the data * Note that some parameter codes accumulate data across the time range. For those @@ -160,7 +162,9 @@ export const getIVDataSegments = memoize(dataKind => createSelector( } const getNewSegment = function(point) { return { - ...point, + isMasked: point.isMasked, + label: point.label, + class: point.class, points: [] }; }; @@ -180,8 +184,7 @@ export const getIVDataSegments = memoize(dataKind => createSelector( const hasGap = point.dateTime - previousDate >= SEVENTY_TWO_MINUTES; const pointLabelHasChanged = newSegment.label !== point.label; - if (!newSegment.isMasked && !point.isMasked && hasGap) { - // there is a gap between two line segments so start a new segment + if (!newSegment.isMasked && !point.isMasked && hasGap) {// there is a gap between two line segments so start a new segment segments.push(newSegment); newSegment = getNewSegment(point); diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/iv-data.test.js b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/iv-data.test.js new file mode 100644 index 0000000000000000000000000000000000000000..9b5d5e5bad7daacc2c682164941cb4ab34c30893 --- /dev/null +++ b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/iv-data.test.js @@ -0,0 +1,213 @@ + +import {getIVDataPoints, getIVTableData, getIVDataSegments, getIVUniqueDataKinds} from './iv-data'; + +describe('monitoring-location/components/hydrograph/selectors/iv-data', () => { + const TEST_IV_DATA = { + parameter: { + parameterCode: '72019', + name: 'Depth to water level' + }, + values: { + '90649': { + points: [ + {value: 24.2, qualifiers: ['A'], dateTime: 1582560900000}, + {value: 24.1, qualifiers: ['A'], dateTime: 1582561800000}, + {value: null, qualifiers: ['A', 'ICE'], dateTime: 1582562700000}, + {value: null, qualifiers: ['A', 'ICE'], dateTime: 1582569900000}, + {value: 25.2, qualifiers: ['E'], dateTime: 1582570800000}, + {value: 25.4, qualifiers: ['E'], dateTime: 1600617600000}, + {value: 25.6, qualifiers: ['E'], dateTime: 1600618500000}, + {value: 26.5, qualifiers: ['P'], dateTime: 1600619400000}, + {value: 25.9, qualifiers: ['P'], dateTime: 1600620300000} + ], + method: { + methodID: '90649' + } + } + } + }; + + const TEST_STATE = { + hydrographData: { + primaryIVData: TEST_IV_DATA + }, + hydrographState: { + selectedIVMethodID: '90649' + } + }; + + describe('getIVDataPoints', () => { + it('Returns null if no data of data kind exists', () => { + expect(getIVDataPoints('compare')(TEST_STATE)).toBeNull(); + }); + + it('Returns the iv data points with the correct properties', () => { + const points = getIVDataPoints('primary')(TEST_STATE); + expect(points['90649']).toBeDefined(); + expect(points['90649']).toHaveLength(9); + expect(points['90649'][0]).toEqual({ + value: 24.2, + dateTime: 1582560900000, + isMasked: false, + maskedQualifer: undefined, + approvalQualifier: 'a', + label: 'Approved', + class: 'approved' + }); + expect(points['90649'][2]).toEqual({ + value: null, + dateTime: 1582562700000, + isMasked: true, + maskedQualifier: 'ice', + approvalQualifier: 'a', + label: 'Ice Affected', + class: 'ice-affected-mask' + }); + expect(points['90649'][4]).toEqual({ + value: 25.2, + dateTime: 1582570800000, + isMasked: false, + maskedQualifier: undefined, + approvalQualifier: 'e', + label: 'Estimated', + class: 'estimated' + }); + expect(points['90649'][7]).toEqual({ + value: 26.5, + dateTime: 1600619400000, + isMasked: false, + maskedQualifier: undefined, + approvalQualifier: undefined, + label: 'Provisional', + class: 'provisional' + }); + }); + }); + + describe('getIVTableData', () => { + it('Expect to return an empty array if no IV data', () => { + expect(getIVTableData('compare')(TEST_STATE)).toHaveLength(0); + }); + + it('Expect to return requested IV data with appropriate properties', () => { + const points = getIVTableData('primary')(TEST_STATE); + expect(points).toHaveLength(9); + expect(points[0]).toEqual({ + parameterName: 'Depth to water level', + result: 24.2, + dateTime: '2020-02-24T10:15:00.000-06:00', + approvals: 'Approved', + masks: '' + }); + expect(points[2]).toEqual({ + parameterName: 'Depth to water level', + result: null, + dateTime: '2020-02-24T10:45:00.000-06:00', + approvals: 'Approved', + masks: 'Ice Affected' + }); + expect(points[4]).toEqual({ + parameterName: 'Depth to water level', + result: 25.2, + dateTime: '2020-02-24T13:00:00.000-06:00', + approvals: 'Estimated', + masks: '' + }); + expect(points[7]).toEqual({ + parameterName: 'Depth to water level', + result: 26.5, + dateTime: '2020-09-20T11:30:00.000-05:00', + approvals: 'Provisional', + masks: '' + }); + }); + }); + describe('getIVDataSegments', () => { + it('Expects null if no IV of the data kind exists', () => { + expect(getIVDataSegments('compare')(TEST_STATE)).toBeNull(); + }); + + it('Returns the expected data segments', () => { + const segmentsByMethodID = getIVDataSegments('primary')(TEST_STATE); + expect(segmentsByMethodID['90649']).toBeDefined(); + const segments = segmentsByMethodID['90649']; + expect(segments).toHaveLength(5); + expect(segments[0]).toEqual({ + isMasked: false, + points: [ + {value: 24.2, dateTime: 1582560900000}, + {value: 24.1, dateTime: 1582561800000} + ], + label: 'Approved', + class: 'approved' + }); + expect(segments[1]).toEqual({ + isMasked: true, + points: [ + {value: 24.1, dateTime: 1582561800000}, + {value: null, dateTime: 1582562700000}, + {value: null, dateTime: 1582569900000}, + {value: 25.2, dateTime: 1582570800000} + ], + label: 'Ice Affected', + class: 'ice-affected-mask' + }); + expect(segments[2]).toEqual({ + isMasked: false, + points: [{value: 25.2, dateTime: 1582570800000}], + label: 'Estimated', + class: 'estimated' + }); + expect(segments[3]).toEqual({ + isMasked: false, + points: [ + {value: 25.4, dateTime: 1600617600000}, + {value: 25.6, dateTime: 1600618500000} + ], + label: 'Estimated', + class: 'estimated' + }); + expect(segments[4]).toEqual({ + isMasked: false, + points: [ + {value: 25.6, dateTime: 1600618500000}, + {value: 26.5, dateTime: 1600619400000}, + {value: 25.9, dateTime: 1600620300000} + ], + label: 'Provisional', + class: 'provisional' + }); + }); + }); + + describe('getIVUniqueDataKinds', () => { + it('returns an empty array if no IV data of data kind exists', () => { + expect(getIVUniqueDataKinds('compare')(TEST_STATE)).toHaveLength(0); + }); + + it('Returns expected unique data kind for the IV data', () => { + const uniqueData = getIVUniqueDataKinds('primary')(TEST_STATE); + expect(uniqueData).toHaveLength(4); + expect(uniqueData[0]).toEqual({ + isMasked: false, + label: 'Approved', + class: 'approved' + }); + expect(uniqueData[1]).toEqual({ + isMasked: true, + label: 'Ice Affected', + class: 'ice-affected-mask' + }); + expect(uniqueData[2]).toEqual({ + isMasked: false, + label: 'Estimated', + class: 'estimated' + }); + expect(uniqueData[3]).toEqual({ + isMasked: false, + label: 'Provisional', + class: 'provisional' + }); + }); + }); +}); diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/legend-data.js b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/legend-data.js index 3e5f7c0d82344a2ec8636d43290c0301ec3d40d0..cbadbb181a2ca2d3b3ece5de2d9bc381a27b4673 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/legend-data.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/legend-data.js @@ -37,8 +37,8 @@ const getLegendDisplay = createSelector( anyVisibleGroundwaterLevels, (showCompare, showMedian, medianSeries, currentClasses, compareClasses, showWaterWatch, floodLevels, showGroundWaterLevels) => { return { - primaryIV: currentClasses, - compareIV: showCompare ? compareClasses : undefined, + primaryIV: currentClasses.length ? currentClasses : undefined, + compareIV: showCompare && compareClasses.length ? compareClasses : undefined, median: showMedian ? medianSeries : undefined, floodLevels: showWaterWatch ? floodLevels : undefined, groundwaterLevels: showGroundWaterLevels @@ -49,7 +49,6 @@ const getLegendDisplay = createSelector( const getIVMarkers = function(dataKind, uniqueIVKinds) { let maskMarkers = []; let lineMarkers = []; - const textMarker = defineTextOnlyMarker(TS_LABEL[dataKind]); uniqueIVKinds.forEach(ivKind => { if (ivKind.isMasked) { maskMarkers.push(defineRectangleMarker(null, `mask ${ivKind.class}`, ivKind.label, `url(#${HASH_ID[dataKind]})`)); @@ -57,11 +56,11 @@ const getIVMarkers = function(dataKind, uniqueIVKinds) { return lineMarkers.push(defineLineMarker(null, `line-segment ts-${ivKind.class} ts-${dataKind}`, ivKind.label)); } }); - return [textMarker, ...lineMarkers, ...maskMarkers]; + return [...lineMarkers, ...maskMarkers]; }; /* - * @param {Object} medianMetData + * @param {Object} medianMetaData * @return {Array of Array} - each subarray rpresents the markes for a time series median data */ const getMedianMarkers = function(medianMetaData) { @@ -87,31 +86,35 @@ const getMedianMarkers = function(medianMetaData) { }); }; -const getFloodLevelMarkers = function(floodLevels) { - const FLOOD_LEVEL_DISPLAY = { - actionStage: { - label: 'Action Stage', - class: 'action-stage' - }, - floodStage: { - label: 'Flood Stage', - class: 'flood-stage' - }, - moderateFloodStage: { - label: 'Moderate Flood Stage', - class: 'moderate-flood-stage' - }, - majorFloodStage: { - label: 'Major Flood Stage', - class: 'major-flood-stage' +const floodLevelDisplay = function(floodLevels) { + let floodLevelsForDisplay = {}; + Object.keys(floodLevels).forEach(key => { + if (floodLevels[key]) { + const keyWithCapitalFirstLetter = `${key.charAt(0).toUpperCase()}${key.slice(1)}`; + // Format label by cutting the camel case word at upper case letters + const label = keyWithCapitalFirstLetter.match(/([A-Z]?[^A-Z]*)/g).slice(0,-1); + + Object.assign(floodLevelsForDisplay, + {[key]: { + 'label': [label.join(' ')], + 'class': [label.join('-').toLowerCase()] + }} + ); } - }; - return Object.keys(floodLevels).map((stage) => { + }); + + return floodLevelsForDisplay; +}; + +const getFloodLevelMarkers = function(floodLevels) { + const floodLevelsForDisplay = floodLevelDisplay(floodLevels); + + return Object.keys(floodLevelsForDisplay).map((stage) => { return [ - defineTextOnlyMarker(FLOOD_LEVEL_DISPLAY[stage].label), + defineTextOnlyMarker(floodLevelsForDisplay[stage].label), defineLineMarker( null, - `waterwatch-data-series ${FLOOD_LEVEL_DISPLAY[stage].class}`, + `waterwatch-data-series ${floodLevelsForDisplay[stage].class}`, `${floodLevels[stage]} ft`) ]; }); @@ -127,7 +130,7 @@ const getFloodLevelMarkers = function(floodLevels) { */ export const getLegendMarkerRows = createSelector( getLegendDisplay, - (displayItems) => { + displayItems => { const markerRows = []; let currentTsMarkerRow = displayItems.primaryIV ? getIVMarkers('primary', displayItems.primaryIV) : undefined; const compareTsMarkerRow = displayItems.compareIV ? getIVMarkers('compare', displayItems.compareIV) : undefined; @@ -143,10 +146,10 @@ export const getLegendMarkerRows = createSelector( } } if (currentTsMarkerRow) { - markerRows.push(currentTsMarkerRow); + markerRows.push([defineTextOnlyMarker(TS_LABEL['primary'])].concat(currentTsMarkerRow)); } if (compareTsMarkerRow) { - markerRows.push(compareTsMarkerRow); + markerRows.push([defineTextOnlyMarker(TS_LABEL['compare'])].concat(compareTsMarkerRow)); } markerRows.push(...medianMarkerRows, ...floodMarkerRows); return markerRows; diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/legend-data.test.js b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/legend-data.test.js index 0b7dbb78f53f6949e78bbeecdcf65bd6bca37d84..702b34809476e509016471f28a2271e2d0eec540 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/legend-data.test.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/legend-data.test.js @@ -3,205 +3,105 @@ import {lineMarker, rectangleMarker, textOnlyMarker, circleMarker} from 'd3rende import {getLegendMarkerRows} from './legend-data'; describe('monitoring-location/components/hydrograph/selectors/legend-data', () => { - const TEST_DATA = { - ivTimeSeriesData: { - queryInfo: { - 'current:P7D': { - notes: { - requestDT: 1520339792000, - 'filter:timeRange': { - mode: 'PERIOD', - periodDays: 7, - modifiedSince: null - } - } - } - }, - timeSeries: { - '72019:current:P7D': { - tsKey: 'current:P7D', - startTime: new Date('2018-03-06T15:45:00.000Z'), - endTime: new Date('2018-03-13T13:45:00.000Z'), - variable: '45807197', - points: [{ - value: 10, - qualifiers: ['P'], - approved: false, - estimated: false - }, { - value: null, - qualifiers: ['P', 'ICE'], - approved: false, - estimated: false - }, { - value: null, - qualifiers: ['P', 'FLD'], - approved: false, - estimated: false - }] - }, - - '72019:compare': { - tsKey: 'compare:P7D', - startTime: new Date('2018-03-06T15:45:00.000Z'), - endTime: new Date('2018-03-18T15:45:00.000Z'), - variable: '45807202', - points: [{ - value: 1, - qualifiers: ['A'], - approved: false, - estimated: false - }, { - value: 2, - qualifiers: ['A'], - approved: false, - estimated: false - }, { - value: 3, - qualifiers: ['E'], - approved: false, - estimated: false - }] - } - }, - variables: { - '45807197': { - variableCode: {value: '72019'}, - variableName: 'Groundwater Levels', - variableDescription: 'Depth to water level, ft below land surface', - oid: '45807197' - }, - '45807202': { - variableCode: {value: '00065'}, - variableName: 'Gage height', - oid: '45807202' - } - } + const TEST_PRIMARY_IV_DATA = { + parameter: { + parameterCode: '72019' }, - statisticsData: { - median: { - '72019': { - '1': [{ - month_nu: '2', - day_nu: '25', - p50_va: '43', - begin_yr: '1970', - end_yr: '2017', - loc_web_ds: 'This method' - }] + values: { + '90649': { + points: [ + {value: 12.6, qualifiers: ['P'], dateTime: 1582560900000}, + {value: null, qualifiers: ['ICE'], dateTime: 1582561800000} + ], + method: { + methodID: '90649' } } + } + }; + const TEST_COMPARE_IV_DATA = { + parameter: { + parameterCode: '72019' }, - ivTimeSeriesState: { - currentIVVariableID: '45807197', - currentIVDateRange: 'P7D', - showIVTimeSeries: { - current: true, - compare: true, - median: true - } - }, - discreteData: { - groundwaterLevels: { - '45807197': { - variable: { - variableCode: { - value: '72019' - }, - oid: '45807197' - }, - values: [ - {value: '14.0', dateTime: 1519942619200}, - {value: '14.5', dateTime: 1490882400000}, - {value: '13.0', dateTime: 1490536800000}, - {value: '12.0', dateTime: 1489672800000} - ] + values: { + '90649': { + points: [ + {value: 12.3, qualifiers: ['A'], dateTime: 1582560900000}, + {value: 14.0, qualifiers: ['E'], dateTime: 1582561800000} + ], + method: { + methodID: '90649' } } - }, - floodData: { - floodLevels: { - site_no: '07144100', - action_stage: '20', - flood_stage: '22', - moderate_flood_stage: '25', - major_flood_stage: '26' - } } }; - describe('getLegendMarkerRows', () => { - - it('Should return no markers if no time series to show', () => { - let newData = { - ...TEST_DATA, - ivTimeSeriesData: { - ...TEST_DATA.ivTimeSeriesData, - timeSeries: {} - }, - statisticsData: {}, - floodState: {}, - discreteData: {} - }; - - expect(getLegendMarkerRows(newData)).toEqual([]); - }); + const TEST_GROUNDWATER_LEVELS = { + parameter: { + parameterCode: '72019' + }, + values: [ + {value: 11.3, qualifiers: ['R'], dateTime: 1582560900000}, + {value: 12.2, qualifiers: ['A'], dateTime: 1582561800000} + ] + }; - it('Should return markers for the selected variable', () => { - const result = getLegendMarkerRows(TEST_DATA); + const TEST_STATE = { + hydrographData: { + currentTimeRange: { + start: 1582560900000, + end: 1582561800000 + }, + prioryearTimeRange: { + start: 1582560900000, + end: 1582561800000 + }, + primaryIVData: TEST_PRIMARY_IV_DATA, + compareIVData: TEST_COMPARE_IV_DATA, + groundwaterLevels: TEST_GROUNDWATER_LEVELS + }, + hydrographState: { + showCompareIVData: false, + showMedianData: false, + selectedIVMethodID: '90649' + }, + floodData: {} + }; - expect(result).toHaveLength(2); - expect(result[0]).toHaveLength(5); - expect(result[0][0].type).toEqual(textOnlyMarker); - expect(result[0][1].type).toEqual(lineMarker); - expect(result[0][2].type).toEqual(rectangleMarker); - expect(result[0][3].type).toEqual(rectangleMarker); - expect(result[0][4].type).toEqual(circleMarker); - expect(result[1]).toHaveLength(2); - expect(result[1][0].type).toEqual(textOnlyMarker); - expect(result[1][1].type).toEqual(lineMarker); + describe('getLegendMarkerRows', () => { + it('Should return no data to show', () => { + expect(getLegendMarkerRows({ + ...TEST_STATE, + hydrographData: {} + })).toHaveLength(0); }); - it('Should return markers for a different selected variable', () => { - const newData = { - ...TEST_DATA, - ivTimeSeriesState: { - ...TEST_DATA.ivTimeSeriesState, - currentIVVariableID: '45807202' - } - }; - const result = getLegendMarkerRows(newData); - - expect(result).toHaveLength(5); - expect(result[0]).toHaveLength(3); - expect(result[0][0].type).toEqual(textOnlyMarker); - expect(result[0][1].type).toEqual(lineMarker); - expect(result[0][2].type).toEqual(lineMarker); + it('Should return markers for primary IV Data', () => { + const markerRows = getLegendMarkerRows(TEST_STATE); + expect(markerRows).toHaveLength(1); + const currentRow = markerRows[0]; + expect(currentRow).toHaveLength(4); + expect(currentRow[0].type).toEqual(textOnlyMarker); + expect(currentRow[1].type).toEqual(lineMarker); + expect(currentRow[2].type).toEqual(rectangleMarker); + expect(currentRow[3].type).toEqual(circleMarker); }); - it('Should return markers only for time series shown', () => { - const newData = { - ...TEST_DATA, - ivTimeSeriesState: { - ...TEST_DATA.ivTimeSeriesState, - showIVTimeSeries: { - 'current': true, - 'compare': false, - 'median': false - } + it('Should return markers for primary and compare when compare is visible', () => { + const markerRows = getLegendMarkerRows({ + ...TEST_STATE, + hydrographState: { + ...TEST_STATE.hydrographState, + showCompareIVData: true } - }; - - const result = getLegendMarkerRows(newData); - - expect(result).toHaveLength(1); - expect(result[0]).toHaveLength(5); - expect(result[0][0].type).toEqual(textOnlyMarker); - expect(result[0][1].type).toEqual(lineMarker); - expect(result[0][2].type).toEqual(rectangleMarker); - expect(result[0][3].type).toEqual(rectangleMarker); - expect(result[0][4].type).toEqual(circleMarker); - + }); + expect(markerRows).toHaveLength(2); + expect(markerRows[0]).toHaveLength(4); + const compareRow = markerRows[1]; + expect(compareRow).toHaveLength(3); + expect(compareRow[0].type).toEqual(textOnlyMarker); + expect(compareRow[1].type).toEqual(lineMarker); + expect(compareRow[2].type).toEqual(lineMarker); }); }); }); \ No newline at end of file diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/parameter-data.js b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/parameter-data.js index 38585757a12bd72d73b675e9442c394a1bccd3c0..29bee221a3cd7b9f59bd766aadcec6353880f658 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/parameter-data.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/parameter-data.js @@ -6,14 +6,15 @@ import {sortedParameters} from 'ui/utils'; /** * Returns a Redux selector function which returns an sorted array of metadata - * for each available parameter code. Each object has the following properties: - * @prop {String} variableID + * for each available parameter . Each object has the following properties: * @prop {String} parameterCode * @prop {String} description - * @prop {Boolean} selected - True if this is the currently selected parameter - * @prop {Number} timeSeriesCount - count of unique time series for this parameter + * @prop {Object} periodOfRecord - with beginDate and endDate String properties + * @prop {Object} waterAlert - with Boolean hasWaterAlert and if true, additional String properties + * (subscriptionParameterCode, displayText, tooltipText) + * Other properties from the Redux store are also included */ -export const getAvailableParameterCodes = createSelector( +export const getAvailableParameters = createSelector( (state) => state.hydrographParameters, allParameters => { if (!Object.keys(allParameters).length) { diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/parameter-data.test.js b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/parameter-data.test.js index 7ba478a086cedc39b7176bc16c6c68021efd3c68..effe6a6d83a1cd402b3fba8542919abeb169130e 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/parameter-data.test.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/parameter-data.test.js @@ -1,6 +1,6 @@ import config from 'ui/config'; -import {getAvailableParameterCodes} from './parameter-data'; +import {getAvailableParameters} from './parameter-data'; describe('monitoring-location/components/hydrograph/selectors/parameter-data', () => { config.ivPeriodOfRecord = { @@ -8,17 +8,13 @@ describe('monitoring-location/components/hydrograph/selectors/parameter-data', ( begin_date: '1980-01-01', end_date: '2020-01-01' }, - '00061': { - begin_date: '1980-02-01', - end_date: '2020-02-01' - }, - '00010': { - begin_date: '1980-03-01', - end_date: '2020-03-01' - }, '72019': { begin_date: '1980-04-01', end_date: '2020-04-01' + }, + '00010': { + begin_date: '1981-04-01', + end_date: '2019-04-01' } }; @@ -27,243 +23,97 @@ describe('monitoring-location/components/hydrograph/selectors/parameter-data', ( begin_date: '1980-03-31', end_date: '2020-03-31' }, - '62611': { + '62610': { begin_date: '1980-05-01', end_date: '2020-05-01' } }; - const TEST_STATE = { - ivTimeSeriesData: { - timeSeries: { - '12345:current:00060': { - description: '00060', - tsKey: 'current:P7D', - variable: 'code0', - points: [{x: 1, y: 2}] - }, - '13456:current:00061': { - description: '00061', - tsKey: 'current:P7D', - variable: 'code1', - points: [{x: 2, y: 3}] - }, - '13457:current:00061': { - description: '00061', - tsKey: 'current:P7D', - variable: 'code1', - points: [{x: 4, y: 6}] - }, - 'current:00010': { - description: '00010', - tsKey: 'current:P7D', - variable: '52331281', - points: [{value: 4}] - }, - 'current:00010F': { - description: '00010', - tsKey: 'current:P7D', - variable: '52331281F', - points: [{value: 4}] - }, - 'current:72019': { - description: '72019', - tsKey: 'current:P7D', - variable: '52331280', - points: [{x: 3, y: 4}] - } - }, - variables: { - 'code0': { - oid: 'code0', - variableDescription: 'code0 desc', - variableCode: { - value: '00060' - } - }, - 'code1': { - oid: 'code1', - variableDescription: 'code1 desc', - variableCode: { - value: '00061' - } - }, - '52331281': { - oid:'52331281', - variableDescription: 'Temperature C', - variableCode: { - value: '00010' - } - }, - '52331281F': { - oid:'52331281F', - variableDescription: 'Temperature F', - variableCode: { - value: '00010F' - } - }, - '52331280': { - oid: '52331280', - variableDescription: 'code2 desc', - variableCode: { - value: '72019' - } - }, - '52331279': { - oid: '52331279', - variableDescription: 'GW level only 62611', - variableCode: { - value: '62611' - } - } - } + + const TEST_PARAMETERS = { + '00060': { + parameterCode: '00060', + name: 'Streamflow, ft3/s', + description: 'Discharge, cubic feet per second', + unit: 'ft3/s', + hasIVData: true }, - ivTimeSeriesState: { - currentIVVariableID: 'code0' + '00010': { + parameterCode: '00010', + name: 'Temperature, water, C', + description: 'Temperature, water, degrees Celsius', + unit: 'deg C', + hasIVData: true + }, + '00010F': { + parameterCode: '00010F', + name: 'Temperature, water, F', + description: 'Temperature, water, degrees Fahrenheit', + unit: 'deg F', + hasIVData: true + }, + '72019': { + parameterCode: '72019', + name: 'Depth to water level, ft below land surface', + description: 'Depth to water level, feet below land surface', + unit: 'ft', + hasIVData: true, + hasGWLevelsData: true }, - discreteData: { - groundwaterLevels: { - '52331280': { - variable: { - variableCode: { - value: '72019', - variableID: 52331280 - }, - variableName: 'Depth to water level, ft below land surface', - variableDescription: 'code2 desc', - unit: { - unitCode: 'ft' - }, - oid: '52331280' - }, - values: [ - { - value: '16.98', - qualifiers: [ - '1' - ], - dateTime: 1479320640000 - }] - }, - '52331279': { - variable: { - variableCode: { - value: '62611', - variableID: 52331279 - }, - variableName: 'Groundwater level above NAVD 1988', - variableDescription: 'Groundwater level 62611', - unit: { - unitCode: 'ft' - }, - oid: '52331279' - }, - values: [ - { - value: '15.02', - qualifiers: [ - '1' - ], - dateTime: 1479320640000 - }] - } - } + '62610': { + parameterCode: '62610', + name: 'Groundwater level above NGVD 1929, feet', + description: 'Groundwater level above NGVD 1929, feet', + unit: 'ft', + hasGWLevelsData: true } }; - describe('getAvailableParameterCodes', () => { + describe('getAvailableParameters', () => { it('Return an empty array if no variables for IV or discrete data groundwater levels are defined', () => { - expect(getAvailableParameterCodes({ - ivTimeSeriesData: {}, - ivTimeSeriesState: {}, - discreteData: {} + expect(getAvailableParameters({ + hydrographParameters: {} })).toHaveLength(0); }); - it('Returns the appropriate variables and metadata', () => { - const result = getAvailableParameterCodes(TEST_STATE); - expect(result).toHaveLength(6); - expect(result[0]).toEqual(expect.objectContaining({ - variableID: 'code0', - parameterCode: '00060', - description:'code0 desc', - selected: true, - timeSeriesCount: 1, - periodOfRecord: { - begin_date: '1980-01-01', - end_date: '2020-01-01' - } - })); - expect(result[0].waterAlert.hasWaterAlert).toBe(true); - expect(result[0].waterAlert.subscriptionParameterCode).toEqual('00060'); - - expect(result[1]).toEqual(expect.objectContaining({ - variableID: '52331280', - parameterCode: '72019', - description: 'code2 desc', - selected: false, - timeSeriesCount: 1, - periodOfRecord: { - begin_date: '1980-03-31', - end_date: '2020-04-01' - } - })); - expect(result[1].waterAlert.hasWaterAlert).toBe(true); - expect(result[1].waterAlert.subscriptionParameterCode).toEqual('72019'); - + it('Expects sorted array of parameter codes', () => { + const parameters = getAvailableParameters({ + hydrographParameters: TEST_PARAMETERS + }); + expect(parameters).toHaveLength(5); + expect(parameters[0].parameterCode).toEqual('00060'); + expect(parameters[0].description).toEqual('Discharge, cubic feet per second'); + expect(parameters[0].periodOfRecord).toEqual({ + begin_date: '1980-01-01', + end_date: '2020-01-01' + }); + expect(parameters[0].waterAlert.hasWaterAlert).toBe(true); + expect(parameters[0].waterAlert.subscriptionParameterCode).toEqual('00060'); - expect(result[2]).toEqual(expect.objectContaining({ - variableID: 'code1', - parameterCode: '00061', - description:'code1 desc', - selected: false, - timeSeriesCount: 2, - periodOfRecord: { - begin_date: '1980-02-01', - end_date: '2020-02-01' - } - })); - expect(result[2].waterAlert.hasWaterAlert).toBe(false); + expect(parameters[1].parameterCode).toEqual('72019'); + expect(parameters[1].description).toEqual('Depth to water level, feet below land surface'); + expect(parameters[1].periodOfRecord).toEqual({ + begin_date: '1980-03-31', + end_date: '2020-04-01' + }); + expect(parameters[1].waterAlert.hasWaterAlert).toBe(true); + expect(parameters[1].waterAlert.subscriptionParameterCode).toEqual('72019'); - expect(result[3]).toEqual(expect.objectContaining({ - variableID: '52331279', - parameterCode: '62611', - description:'GW level only 62611', - selected: false, - timeSeriesCount: 0, - periodOfRecord: { - begin_date: '1980-05-01', - end_date: '2020-05-01' - } - })); - expect(result[3].waterAlert.hasWaterAlert).toBe(false); + expect(parameters[2].parameterCode).toEqual('62610'); + expect(parameters[2].periodOfRecord).toEqual({ + begin_date: '1980-05-01', + end_date: '2020-05-01' + }); + expect(parameters[2].waterAlert.hasWaterAlert).toBe(false); - expect(result[4]).toEqual(expect.objectContaining({ - variableID: '52331281', - parameterCode: '00010', - description:'Temperature C', - selected: false, - timeSeriesCount: 1, - periodOfRecord: { - begin_date: '1980-03-01', - end_date: '2020-03-01' - } - })); - expect(result[4].waterAlert.hasWaterAlert).toBe(true); - expect(result[4].waterAlert.subscriptionParameterCode).toEqual('00010'); + expect(parameters[3].parameterCode).toEqual('00010'); - expect(result[5]).toEqual(expect.objectContaining({ - variableID: '52331281F', - parameterCode: '00010F', - description:'Temperature F', - selected: false, - timeSeriesCount: 1, - periodOfRecord: { - begin_date: '1980-03-01', - end_date: '2020-03-01' - } - })); - expect(result[5].waterAlert.hasWaterAlert).toBe(true); - expect(result[5].waterAlert.subscriptionParameterCode).toEqual('00010'); + expect(parameters[4].parameterCode).toEqual('00010F'); + expect(parameters[4].periodOfRecord).toEqual({ + begin_date: '1981-04-01', + end_date: '2019-04-01' + }); + expect(parameters[4].waterAlert.hasWaterAlert).toBe(true); + expect(parameters[4].waterAlert.subscriptionParameterCode).toEqual('00010'); }); }); }); \ No newline at end of file diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/scales.test.js b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/scales.test.js index 5c259e0fb5ca9d179449156c7cf07c23d495a5fb..d3c386a7cff5450d2988215b77a1c885b374bdfc 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/scales.test.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/scales.test.js @@ -3,10 +3,10 @@ import {DateTime} from 'luxon'; import * as utils from 'ui/utils'; -import {createXScale, createYScale, getTimeRange, getMainYScale, getBrushYScale} from './scales'; +import {createXScale, createYScale, getGraphTimeRange, getMainYScale, getBrushYScale} from './scales'; -describe('monitoring-location/components/hydrograph/scales', () => { +describe('monitoring-location/components/hydrograph/selector/scales', () => { utils.mediaQuery = jest.fn().mockReturnValue(true); const timeZone = 'America/Los_Angeles'; @@ -54,7 +54,7 @@ describe('monitoring-location/components/hydrograph/scales', () => { expect(range[1]).toEqual(0); }); - it('WDFN-66: yScale deals with range [0,1] correctly', () => { + it('yScale deals with range [0,1] correctly', () => { expect(yScale(1)).not.toBeNaN(); expect(yScale(.5)).not.toBeNaN(); expect(yScale(.999)).not.toBeNaN(); @@ -104,119 +104,42 @@ describe('monitoring-location/components/hydrograph/scales', () => { expect(singleLinear10 - singleLinear20).toBeCloseTo(singleLinear20 - singleLinear30); }); - describe('getTimeRange', () => { + describe('getGraphTimeRange', () => { const TEST_STATE = { - ianaTimeZone: 'America/Chicago', - ivTimeSeriesData: { - queryInfo: { - 'current:P7D': { - notes: { - requestDT: 1490936400000, - 'filter:timeRange': { - mode: 'PERIOD', - periodDays: 7, - modifiedSince: null - } - } - } + hydrographData: { + currentTimeRange: { + start: 1613511058824, + end: 1614115858824 } }, - ivTimeSeriesState: { - currentIVDateRange: 'P7D', - ivGraphBrushOffset: null + hydrographState: { + graphBrushOffset: { + start: 10000000000, + end: 500000 + } } }; - it('Return full time range if not brush offset is set for both BRUSH and MAIN', () => { - expect(getTimeRange('BRUSH', 'current')(TEST_STATE)).toEqual({ - start: 1490331600000, - end: 1490936400000 - }); - expect(getTimeRange('MAIN', 'current')(TEST_STATE)).toEqual({ - start: 1490331600000, - end: 1490936400000 - }); + it('If graph is BRUSH, the time range is always used the time range', () => { + expect(getGraphTimeRange('BRUSH', 'current')(TEST_STATE)).toEqual(TEST_STATE.hydrographData.currentTimeRange); }); - it('Return the full time range for BRUSH but reduce MAIN by brush offset', () => { - const testState = { + it('If graph is MAIN and the brush offset is null the time range is the full range', () => { + expect(getGraphTimeRange('MAIN', 'current')({ ...TEST_STATE, - ivTimeSeriesState: { - ...TEST_STATE.ivTimeSeriesState, - ivGraphBrushOffset: { - start: 100000, - end: 900000 - } + hydrographState: { + ...TEST_STATE.hydrographState, + graphBrushOffset: null } - }; - expect(getTimeRange('BRUSH', 'current')(testState)).toEqual({ - start: 1490331600000, - end: 1490936400000 - }); - expect(getTimeRange('MAIN', 'current')(testState)).toEqual({ - start: 1490331700000, - end: 1490935500000 - }); - }); - }); - - describe('getMainYScale', () => { - - it('Creates a scale when there is no initial data', () => { - const STATE = { - ivTimeSeriesData: {}, - statisticsData: {}, - ivTimeSeriesState: { - showIVTimeSeries: { - current: true, - compare: false, - median: false - }, - currentVariableID: null - }, - ui: { - width: 200, - windowWidth: 600 - }, - discreteData: {} - }; - expect(getMainYScale(STATE).name).toBe('scale'); - expect(getBrushYScale(STATE).name).toBe('scale'); + })).toEqual(TEST_STATE.hydrographData.currentTimeRange); }); - it('Creates a scale when there is initial data', () => { - const STATE = { - ivTimeSeriesData: { - variables: { - '00060ID': { - variableCode: { - value: '00060' - } - } - }, - timeSeries: { - '00060ID': { - variable: '00060ID', - points: [] - } - } - }, - statisticsData: {}, - ivTimeSeriesState: { - showIVTimeSeries: { - current: true, - compare: false - }, - currentIVVariableID: '00060ID' - }, - ui: { - width: 200, - windowWidth: 600 - }, - discreteData: {} - }; - expect(getMainYScale(STATE).name).toBe('scale'); - expect(getBrushYScale(STATE).name).toBe('scale'); + it('If graph is main the time range will be the time range with the brush offset applied', () => { + const state = TEST_STATE.hydrographState; + expect(getGraphTimeRange('MAIN', 'current')(TEST_STATE)).toEqual({ + start: TEST_STATE.hydrographData.currentTimeRange.start + state.graphBrushOffset.start, + end: TEST_STATE.hydrographData.currentTimeRange.end - state.graphBrushOffset.end + }); }); }); }); diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/time-series-data.js b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/time-series-data.js index fb83dcf5fed2d02ec60cbc0c9455409a3a6fe299..c081ec0e1e15e1f107ccecf8c8a29ba8124820a7 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/time-series-data.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/time-series-data.js @@ -11,11 +11,9 @@ const formatTime = function(timeInMillis) { return DateTime.fromMillis(timeInMillis, {zone: config.locationTimeZone}).toFormat('L/d/yyyy tt ZZZ'); }; /** - * Factory function creates a function that: - * Returns the current show state of a time series. - * @param {Object} state Redux store - * @param {String} tsKey Time series key - * @return {Boolean} Show state of the time series + * Returns a Redux selector function which returns whether the dataKind time series is visible + * @param {String} dataKind - 'primary', 'compare', 'median' + * @return {Function} */ export const isVisible = memoize(dataKind => createSelector( isCompareIVDataVisible, @@ -34,24 +32,9 @@ export const isVisible = memoize(dataKind => createSelector( }) ); -/** - * Returns a Redux selector function which returns the label to be used for the Y axis - */ -export const getYLabel = createSelector( - getPrimaryParameter, - parameter => parameter ? parameter.description : '' -); - -/* - * Returns a Redux selector function which returns the label to be used for the secondary y axis - */ -export const getSecondaryYLabel= function() { - return ''; // placeholder for ticket WDFN-370 -}; - - /** * Returns a Redux selector function which returns the title to be used for the hydrograph + * @return {Function} */ export const getTitle = createSelector( getPrimaryParameter, @@ -59,9 +42,9 @@ export const getTitle = createSelector( getPrimaryMethods, (parameter, methodID, methods) => { let title = parameter ? parameter.name : ''; - if (methodID && methods.length) { + if (methodID && methods.length > 1) { const thisMethod = methods.find(method => method.methodID === methodID); - if (thisMethod.methodDescription) { + if (thisMethod && thisMethod.methodDescription) { title = `${title}, ${thisMethod.methodDescription}`; } } @@ -72,6 +55,7 @@ export const getTitle = createSelector( /* * Returns a Redux selector function which returns the description of the hydrograph + * @return {Function} */ export const getDescription = createSelector( getPrimaryParameter, @@ -85,6 +69,10 @@ export const getDescription = createSelector( } ); +/* + * Returns a Redux selector function which returns the primary parameter's unit code. + * @return {Function} + */ export const getPrimaryParameterUnitCode = createSelector( getPrimaryParameter, parameter => parameter ? parameter.unit : null diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/time-series-data.test.js b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/time-series-data.test.js index 6b6425718a28939b9f2c9c5fa1b66fec9d92466b..6a5e03a44714a2450ad761667296917bf9375bf2 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/time-series-data.test.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/time-series-data.test.js @@ -1,311 +1,133 @@ -import { - isVisible, getYLabel, getTitle, - getDescription, getTsTimeZone} from './time-series-data'; +import config from 'ui/config'; +import {isVisible, getTitle, getDescription, getPrimaryParameterUnitCode} from './time-series-data'; -const TEST_DATA = { - ianaTimeZone: 'America/Chicago', - ivTimeSeriesData: { - timeSeries: { - '00060': { - tsKey: 'current:P7D', - startTime: 1520351100000, - endTime: 1520948700000, - variable: '45807197', - method: 69929, - points: [{ - value: 10, - qualifiers: ['P'], - approved: false, - estimated: false - }, { - value: null, - qualifiers: ['P', 'ICE'], - approved: false, - estimated: false - }, { - value: null, - qualifiers: ['P', 'FLD'], - approved: false, - estimated: false - }] - }, - '00010': { - tsKey: 'compare:P7D', - startTime: 1520351100000, - endTime: 1520948700000, - variable: '45807196', - method: 69931, - points: [{ - value: 1, - qualifiers: ['P'], - approved: false, - estimated: false - }, { - value: 2, - qualifiers: ['P'], - approved: false, - estimated: false - }, { - value: 3, - qualifiers: ['P'], - approved: false, - estimated: false - }] - }, - '00010:2': { - tsKey: 'current:P7D', - startTime: 1520351100000, - endTime: 1520948700000, - variable: '45807196', - method: 69930, - points: [{ - value: 1, - qualifiers: ['P'], - approved: false, - estimated: false - }, { - value: 2, - qualifiers: ['P'], - approved: false, - estimated: false - }, { - value: 3, - qualifiers: ['P'], - approved: false, - estimated: false - }] - }, - '00011': { - tsKey: 'compare:P7D', - startTime: 1520351100000, - endTime: 1520948700000, - variable: '45807195', - method: 69929, - points: [{ - value: 68, - qualifiers: ['P'], - approved: false, - estimated: false - }, { - value: 70, - qualifiers: ['P'], - approved: false, - estimated: false - }, { - value: 77, - qualifiers: ['P'], - approved: false, - estimated: false - }] - }, - '00060:P30D': { - tsKey: 'current:P30D:00060', - startTime: 1520351100000, - endTime: 1520948700000, - variable: '45807197', - method: 69929, - points: [{ - value: 10, - qualifiers: ['P'], - approved: false, - estimated: false - }, { - value: null, - qualifiers: ['P', 'ICE'], - approved: false, - estimated: false - }, { - value: null, - qualifiers: ['P', 'FLD'], - approved: false, - estimated: false - }] - } - }, - timeSeriesCollections: { - 'coll1': { - variable: 45807197, - timeSeries: ['00060'] - } - }, - requests: { - 'current:P7D': { - timeSeriesCollections: ['coll1'] - }, - 'current:P30D:00060': {} - }, - variables: { - '45807197': { - variableCode: { - value: '00060' - }, - variableName: 'Streamflow', - variableDescription: 'Discharge, cubic feet per second', - oid: '45807197' - }, - '45807196': { - variableCode: { - value: '00010' - }, - variableName: 'Temperature, water, degrees Celsius', - variableDescription: 'Water Temperature in Celsius', - oid: '45807196' - }, - '45807195': { - variableCode: { - value: '00011' - }, - variableName: 'Temperature, water, degrees Fahrenheit', - variableDescription: 'Water Temperature in Fahrenheit', - oid: '45807195' - }, - '55807196' : { - variableCode: { - value: '11111' - }, - variableName: 'My variable', - variableDescription: 'My awesome variable', - oid: '55807196' - } + +const TEST_STATE = { + hydrographData: { + currentTimeRange: { + start: 1613569918466, + end: 1614174718466 }, - methods: { - 69329: { - methodDescription: '', - methodID: 69928 + primaryIVData: { + parameter: { + parameterCode:'00030', + name: 'Dissolved oxygen, water, unfiltered, mg/L', + description: 'Dissolved oxygen, water, unfiltered, milligrams per liter', + unit: 'mg/l' }, - 69330: { - methodDescription: '4.1 ft from riverbed (middle)', - methodID: 69930 - }, - 69331: { - methodDescription: '1.0 ft from riverbed (bottom)', - methodID: 69931 - } - }, - queryInfo: { - 'current:P7D': { - notes: { - requestDT: 1483994767572, - 'filter:timeRange': { - mode: 'PERIOD', - periodDays: 7, - modifiedSince: null + values: { + '69937': { + points: [], + method: { + methodDescription: 'From multiparameter sonde, [Discontinued]', + methodID: '69937' } - } - }, - 'current:P30D:00060': { - notes: { - requestDT: 1483994767572, - 'filter:timeRange': { - mode: 'RANGE', - interval: { - start: 1483941600000, - end: 1486533600000 - }, - modifiedSince: null + }, + '252055': { + points: [], + method: { + methodDescription: 'From multiparameter sonde', + methodID: '252055' } } } } }, - ivTimeSeriesState: { - currentIVVariableID: '45807197', - currentIVDateRange: 'P7D' + hydrographState: { + showCompareIVData: false, + showMedianData: false, + selectedIVMethodID: '252055' } }; describe('monitoring-location/components/hydrograph/time-series module', () => { - + config.locationTimeZone = 'America/Chicago'; describe('isVisible', () => { it('Returns whether the time series is visible', () => { - const store = { - ivTimeSeriesState: { - showIVTimeSeries: { - 'current': true, - 'compare': false, - 'median': true - } + expect(isVisible('primary')(TEST_STATE)).toBe(true); + expect(isVisible('compare')(TEST_STATE)).toBe(false); + expect(isVisible('compare')({ + ...TEST_STATE, + hydrographState: { + ...TEST_STATE.hydrographState, + showCompareIVData: true } - }; - - expect(isVisible('current')(store)).toBe(true); - expect(isVisible('compare')(store)).toBe(false); - expect(isVisible('median')(store)).toBe(true); - }); - }); - - describe('yLabelSelector', () => { - it('Returns string to be used for labeling the y axis', () => { - expect(getYLabel(TEST_DATA)).toBe('Discharge, cubic feet per second'); - }); - - it('Returns empty string if no variable selected', () => { - expect(getYLabel({ - ...TEST_DATA, - ivTimeSeriesState: { - ...TEST_DATA.ivTimeSeriesState, - currentIVVariableID: null + })).toBe(true); + expect(isVisible('median')(TEST_STATE)).toBe(false); + expect(isVisible('median')({ + ...TEST_STATE, + hydrographState: { + ...TEST_STATE.hydrographState, + showMedianData: true } - })).toBe(''); + })).toBe(true); }); }); describe('getTitle', () => { - it('Returns the string to used for graph title', () => { - expect(getTitle(TEST_DATA)).toBe('Streamflow'); - }); - it('Returns the title string with the method description appended', () => { + it('Returns an empty if no IV data exists', () => { expect(getTitle({ - ...TEST_DATA, - ivTimeSeriesState: { - ...TEST_DATA.ivTimeSeriesState, - currentIVMethodID: 69330 - } - })).toBe('Streamflow' + ', ' + '4.1 ft from riverbed (middle)'); + hydrographData: {}, + hydrographState: {} + })).toBe(''); }); - it('Returns empty string if no variable selected', () => { + + it('Returns the parameter name if only one method is defined', () => { expect(getTitle({ - ...TEST_DATA, - ivTimeSeriesState: { - ...TEST_DATA.ivTimeSeriesState, - currentIVVariableID: null + ...TEST_STATE, + hydrographData: { + ...TEST_STATE.hydrographData, + primaryIVData: { + ...TEST_STATE.hydrographData.primaryIVData, + values: { + '252055': { + ...TEST_STATE.hydrographData.primaryIVData.values['252055'] + } + } + } } - })).toBe(''); + })).toBe('Dissolved oxygen, water, unfiltered, mg/L'); + }); + + it('Returns the parameter name with the method description if more than one method is defined', () => { + expect(getTitle(TEST_STATE)).toBe('Dissolved oxygen, water, unfiltered, mg/L, From multiparameter sonde'); }); }); describe('getDescription', () => { - it('Returns a description with the date for the current times series', () => { - const result = getDescription(TEST_DATA); - - expect(result).toContain('Discharge, cubic feet per second'); - expect(result).toContain('1/2/2017'); - expect(result).toContain('1/9/2017'); + it('Returns an empty string if no IV data', () => { + expect(getDescription({ + hydrographData: {} + })).toBe(''); }); - }); - describe('getTsTimeZone', () => { + it('Returns the description of the primary parameter as well as the current time range', () => { + expect(getDescription(TEST_STATE)).toBe( + 'Dissolved oxygen, water, unfiltered, milligrams per liter from 2/17/2021 7:51:58 AM -0600 to 2/24/2021 7:51:58 AM -0600'); + }); - it('Returns local if series is empty', () => { - const result = getTsTimeZone({ - series: {} - }); - expect(result).toEqual('local'); + it('Returns the description without time if current time range is missing', () => { + expect(getDescription({ + ...TEST_STATE, + hydrographData: { + primaryIVData: { + ...TEST_STATE.hydrographData.primaryIVData + } + } + })).toBe('Dissolved oxygen, water, unfiltered, milligrams per liter'); }); + }); - it('Returns local if timezone is null', () => { - const result = getTsTimeZone({ - ianaTimeZone: null - }); - expect(result).toEqual('local'); + describe('getPrimaryParameterUnitCode', () => { + it('Return null if no primary IV data', () => { + expect(getPrimaryParameterUnitCode({ + hydrographData: {} + })).toBeNull(); }); - it('Returns the IANA timezone NWIS and IANA agree', () => { - const result = getTsTimeZone({ - ianaTimeZone: 'America/New_York' - }); - expect(result).toEqual('America/New_York'); + it('Return unit code if primary IV data', () => { + expect(getPrimaryParameterUnitCode(TEST_STATE)).toBe('mg/l'); }); }); }); diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/time-series-graph.js b/assets/src/scripts/monitoring-location/components/hydrograph/time-series-graph.js index aa226d5fa025370d5d1bce6334e674d755bdbf1a..3c29808ad90621c3d103eb5ba8307080622deeb2 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/time-series-graph.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/time-series-graph.js @@ -17,10 +17,9 @@ import {getSelectedIVMethodID} from 'ml/selectors/hydrograph-state-selector'; import {getAxes} from './selectors/axes'; import {getGroundwaterLevelPoints} from './selectors/discrete-data'; import {getIVDataSegments, HASH_ID} from './selectors/iv-data'; - -import {getTitle, getDescription, isVisible} from './selectors/time-series-data'; import {getMainLayout} from './selectors/layout'; import {getMainXScale, getMainYScale, getBrushXScale} from './selectors/scales'; +import {getTitle, getDescription, isVisible} from './selectors/time-series-data'; import {drawGroundwaterLevels} from './discrete-data'; import {drawDataSegments} from './time-series-lines'; @@ -55,7 +54,8 @@ const plotMedianPoints = function(elem, {xscale, yscale, modulo, points}) { .y(function(d) { return yscale(d.point); }); - const medianGrp = elem.append('g'); + const medianGrp = elem.append('g') + .attr('class', 'median-stats-group'); medianGrp.append('path') .datum(points) .classed('median-data-series', true) @@ -148,19 +148,14 @@ const plotAllFloodLevelPoints = function(elem, {visible, xscale, yscale, seriesP }; -const createTitle = function(elem, store, siteNo, showMLName, agencyCode, sitename, showTooltip) { +const drawTitle = function(elem, store, siteNo, agencyCode, sitename, showMLName, showTooltip) { let titleDiv = elem.append('div') .classed('time-series-graph-title', true); if (showMLName) { titleDiv.append('div') - .call(link(store,(elem, {mlName, agencyCode}) => { - elem.attr('class', 'monitoring-location-name-div') - .html(`${mlName}, ${agencyCode} ${siteNo}`); - }, createStructuredSelector({ - mlName: sitename, - agencyCode: agencyCode - }))); + .attr('class', 'monitoring-location-name-div') + .html(`${sitename}, ${agencyCode} ${siteNo}`); } titleDiv.append('div') .call(link(store,(elem, {title, parameter}) => { @@ -174,7 +169,7 @@ const createTitle = function(elem, store, siteNo, showMLName, agencyCode, sitena }))); }; -const watermark = function(elem, store) { +const drawWatermark = function(elem, store) { // These constants will need to change if the watermark svg is updated const watermarkHalfHeight = 87 / 2; const watermarkHalfWidth = 235 / 2; @@ -207,14 +202,13 @@ const watermark = function(elem, store) { */ export const drawTimeSeriesGraph = function(elem, store, siteNo, agencyCode, sitename, showMLName, showTooltip) { let graphDiv; - graphDiv = elem.append('div') .attr('class', 'hydrograph-container') .attr('ga-on', 'click') .attr('ga-event-category', 'hydrograph-interaction') .attr('ga-event-action', 'clickOnTimeSeriesGraph') - .call(watermark, store) - .call(createTitle, store, siteNo, agencyCode, sitename, showMLName, showTooltip); + .call(drawWatermark, store) + .call(drawTitle, store, siteNo, agencyCode, sitename, showMLName, showTooltip); if (showTooltip) { graphDiv.call(drawTooltipText, store); } diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/time-series-graph.test.js b/assets/src/scripts/monitoring-location/components/hydrograph/time-series-graph.test.js index b01ab1f5d17a18fd623c73d02233bf1ec28eed71..8516a1aa0377bb55f4ed351c267735462756dabc 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/time-series-graph.test.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/time-series-graph.test.js @@ -1,194 +1,33 @@ -import {select, selectAll} from 'd3-selection'; +import {select} from 'd3-selection'; import * as utils from 'ui/utils'; import {configureStore} from 'ml/store'; -import {Actions} from 'ml/store/instantaneous-value-time-series-state'; +import {setMedianDataVisibility} from 'ml/store/hydrograph-state'; +import {TEST_PRIMARY_IV_DATA, TEST_GW_LEVELS, TEST_MEDIAN_DATA, + TEST_CURRENT_TIME_RANGE +} from './mock-hydrograph-state'; import {drawTimeSeriesGraph} from './time-series-graph'; const TEST_STATE = { - ianaTimeZone: 'America/Chicago', - ivTimeSeriesData: { - timeSeries: { - '2:00010:current': { - points: [{ - dateTime: 1514926800000, - value: 4, - qualifiers: ['P'] - }], - method: '2', - tsKey: 'current:P7D', - variable: '45807190' - }, - '1:00060:current': { - points: [{ - dateTime: 1514926800000, - value: 10, - qualifiers: ['P'] - }, - { - dateTime: 1514928800000, - value: 10, - qualifiers: ['P'] - }], - method: '1', - tsKey: 'current:P7D', - variable: '45807197' - }, - '1:00060:compare': { - points: [{ - dateTime: 1514926800000, - value: 10, - qualifiers: ['P'] - }], - method: '1', - tsKey: 'compare:P7D', - variable: '45807197' - } - }, - timeSeriesCollections: { - 'coll1': { - variable: '45807197', - timeSeries: ['00060:current'] - }, - 'coll2': { - variable: '45807197', - timeSeries: ['00060:compare'] - }, - 'coll3': { - variable: '45807197', - timeSeries: ['00060:median'] - }, - 'coll4': { - variable: '45807190', - timeSeries: ['00010:current'] - } - }, - siteCodes: { - '12345678': { - agencyCode: 'USGS' - } - }, - sourceInfo: { - '12345678': { - siteName: 'Monitoring Location for Test' - } - - }, - queryInfo: { - 'current:P7D': { - notes: { - 'filter:timeRange': { - mode: 'PERIOD', - periodDays: 7 - }, - requestDT: 1514926800000 - } - } - }, - requests: { - 'current:P7D': { - timeSeriesCollections: ['coll1'] - }, - 'compare:P7D': { - timeSeriesCollections: ['coll2', 'col4'] - } - }, - variables: { - '45807197': { - variableCode: { - value: '00060' - }, - oid: '45807197', - variableName: 'Test title for 00060', - variableDescription: 'Test description for 00060', - unit: { - unitCode: 'unitCode' - } - }, - '45809999': { - variableCode: { - value: '00065' - }, - oid: '45809999', - variableName: 'Test title for 00065', - variableDescription: 'Test description for 00065', - unit: { - unitCode: 'unitCode' - } - }, - '45807190': { - variableCode: { - value: '00010' - }, - oid: '45807190', - unit: { - unitCode: 'unitCode' - } - } - }, - methods: { - '1': { - methodDescription: 'method description' - } - } - }, - statisticsData : { - median: { - '00060': { - '1234': [ - { - month_nu: '2', - day_nu: '20', - p50_va: '40', - begin_yr: '1970', - end_yr: '2017', - loc_web_ds: 'This method' - }, { - month_nu: '2', - day_nu: '21', - p50_va: '41', - begin_yr: '1970', - end_yr: '2017', - loc_web_ds: 'This method' - }, { - month_nu: '2', - day_nu: '22', - p50_va: '42', - begin_yr: '1970', - end_yr: '2017', - loc_web_ds: 'This method' - } - ] - } - } + hydrographData: { + primaryIVData: TEST_PRIMARY_IV_DATA, + groundwaterLevels: TEST_GW_LEVELS, + medianStatisticsData: TEST_MEDIAN_DATA, + currentTimeRange: TEST_CURRENT_TIME_RANGE }, - ivTimeSeriesState: { - currentIVVariableID: '45807197', - ivGraphCursorOffset: 0, - currentIVMethodID: 1, - currentIVDateRange: 'P7D', - showIVTimeSeries: { - current: true, - compare: true, - median: true - }, - loadingIVTSKeys: [] + hydrographState: { + selectedIVMethodID: '90649', + showCompareIVData: false, + showMedianData: false, + graphCursorOffset: 5000000 }, ui: { width: 400 }, - floodData: { - floodLevels: { - site_no: '07144100', - action_stage: '20', - flood_stage: '22', - moderate_flood_stage: '25', - major_flood_stage: '26' - } - } + floodData: {} }; describe('monitoring-location/components/hydrograph/time-series-graph', () => { @@ -199,8 +38,7 @@ describe('monitoring-location/components/hydrograph/time-series-graph', () => { let store; beforeEach(() => { - div = select('body').append('div') - .attr('id', 'hydrograph'); + div = select('body').append('div').attr('id', 'hydrograph'); store = configureStore(TEST_STATE); }); @@ -208,164 +46,65 @@ describe('monitoring-location/components/hydrograph/time-series-graph', () => { div.remove(); }); - it('single data point renders', () => { - div.call(drawTimeSeriesGraph, store, '12345678', false, false); - let svgNodes = selectAll('svg'); + it('Render graph title with monitoring location name in title and tooltip', () => { + drawTimeSeriesGraph(div, store, '11112222', 'USGS', 'This site', true, true); - expect(svgNodes.size()).toBe(1); - expect(div.html()).toContain('hydrograph-container'); + const titleDiv = div.select('.time-series-graph-title'); + expect(titleDiv.size()).toBe(1); + expect(titleDiv.select('.monitoring-location-name-div').size()).toBe(1); + expect(titleDiv.select('.usa-tooltip').size()).toBe(1); }); - describe('container display', () => { + it('Render graph title without monitoring location name in title', () => { + drawTimeSeriesGraph(div, store, '11112222', 'USGS', 'This site', false, true); - it('should not be hidden tag if there is data', () => { - div.call(drawTimeSeriesGraph, store, '12345678', false, false); - expect(select('#hydrograph').attr('hidden')).toBeNull(); - }); + const titleDiv = div.select('.time-series-graph-title'); + expect(titleDiv.size()).toBe(1); + expect(titleDiv.select('.monitoring-location-name-div').size()).toBe(0); + expect(titleDiv.select('.usa-tooltip').size()).toBe(1); }); - describe('SVG has been made accessible', () => { - let svg; - beforeEach(() => { - div.call(drawTimeSeriesGraph, store, '12345678', false, false); - svg = select('svg'); - }); - - it('title and desc attributes are present', function() { - const descText = svg.select('desc').html(); - - expect(svg.select('title').html()).toEqual('Test title for 00060, method description'); - expect(descText).toContain('Test description for 00060'); - expect(descText).toContain('12/26/2017'); - expect(descText).toContain('1/2/2018'); - expect(svg.attr('aria-labelledby')).toContain('title'); - expect(svg.attr('aria-describedby')).toContain('desc'); - }); - - it('svg should be focusable', function() { - expect(svg.attr('tabindex')).toBe('0'); - }); - - it('should have a defs node', () => { - expect(selectAll('defs').size()).toBe(1); - expect(selectAll('defs mask').size()).toBe(1); - expect(selectAll('defs pattern').size()).toBe(2); - }); + it('Render graph title without info toolip', () => { + drawTimeSeriesGraph(div, store, '11112222', 'USGS', 'This site', true, false); - it('should render time series data as a line', () => { - // There should be one segment per time-series. Each is a single - // point, so should be a circle. - expect(selectAll('.hydrograph-svg .line-segment').size()).toBe(2); - }); + const titleDiv = div.select('.time-series-graph-title'); + expect(titleDiv.size()).toBe(1); + expect(titleDiv.select('.monitoring-location-name-div').size()).toBe(1); + expect(titleDiv.select('.usa-tooltip').size()).toBe(0); }); - //TODO: Consider adding a test which checks that the y axis is rescaled by - // examining the contents of the text labels. + it('Should render tooltip text and focus circles', () => { + drawTimeSeriesGraph(div, store, '11112222', 'USGS', 'This site', false, true); - describe('compare line', () => { - - beforeEach(() => { - div.call(drawTimeSeriesGraph, store, '12345678', false, false); - }); - - it('Should render one lines', () => { - expect(selectAll('#ts-compare-group .line-segment').size()).toBe(1); - }); - - it('Should remove the lines when removing the compare time series', () => { - store.dispatch(Actions.setIVTimeSeriesVisibility('compare', false)); - return new Promise(resolve => { - window.requestAnimationFrame(() => { - expect(selectAll('#ts-compare-group .line-segment').size()).toBe(0); - resolve(); - }); - }); - }); + expect(div.selectAll('.tooltip-text-group').size()).toBe(1); + expect(div.selectAll('.focus-line-group').size()).toBe(1); + expect(div.selectAll('.focus-circle').size()).toBe(2); }); - describe('median lines', () => { - - beforeEach(() => { - div.call(drawTimeSeriesGraph, store, '12345678', false, false); - }); - - it('Should render one lines', () => { - expect(selectAll('#median-points .median-data-series').size()).toBe(1); - }); + it('Should not render tooltip text and focus circles', ()=> { + drawTimeSeriesGraph(div, store, '11112222', 'USGS', 'This site', false, false); - it('Should remove the lines when removing the median statistics data', () => { - store.dispatch(Actions.setIVTimeSeriesVisibility('median', false)); - return new Promise(resolve => { - window.requestAnimationFrame(() => { - expect(selectAll('#median-points .median-data-series').size()).toBe(0); - resolve(); - }); - }); - }); + expect(div.selectAll('.tooltip-text-group').size()).toBe(0); + expect(div.selectAll('.focus-line-group').size()).toBe(0); + expect(div.selectAll('.focus-circle').size()).toBe(0); }); - describe('flood level lines', () => { + it('Should render current IV Data and groundwater levels but not median steps', () => { + drawTimeSeriesGraph(div, store, '11112222', 'USGS', 'This site', false, false); - beforeEach(() => { - div.call(drawTimeSeriesGraph, store, '12345678', false, false); - }); - - it('Should render four lines', () => { - store.dispatch(Actions.setCurrentIVVariable(45809999)); - div.call(drawTimeSeriesGraph, store, '12345678', false, false); - expect(selectAll('#flood-level-points .action-stage').size()).toBe(1); - expect(selectAll('#flood-level-points .flood-stage').size()).toBe(1); - expect(selectAll('#flood-level-points .moderate-flood-stage').size()).toBe(1); - expect(selectAll('#flood-level-points .major-flood-stage').size()).toBe(1); - }); - - - it('Should remove the lines when removing the waterwatch flood levels data', () => { - return new Promise(resolve => { - window.requestAnimationFrame(() => { - expect(selectAll('#flood-level-points').size()).toBe(0); - resolve(); - }); - }); - }); - }); - - describe('monitoring location name', () => { - it('Should not render the monitoring location name if showMLName is false', () => { - div.call(drawTimeSeriesGraph, store, '12345678', false, false); - - expect(div.selectAll('.monitoring-location-name-div').size()).toBe(0); - }); - - it('Should render the monitoring location if showMLName is true', () => { - div.call(drawTimeSeriesGraph, store, '12345678', true, false); - - const nameDiv = div.selectAll('.monitoring-location-name-div'); - const nameContents = nameDiv.html(); - expect(nameDiv.size()).toBe(1); - expect(nameContents).toContain('Monitoring Location for Test'); - expect(nameContents).toContain('USGS'); - expect(nameContents).toContain('12345678'); - }); + expect(div.selectAll('.ts-primary-group').size()).toBe(1); + expect(div.selectAll('.ts-compare-group').size()).toBe(0); + expect(div.selectAll('.iv-graph-gw-levels-group').size()).toBe(1); + expect(div.selectAll('.median-stats-group').size()).toBe(0); }); - describe('tooltip text and focus elements', () => { - it('Should not render the tooltip if showTooltip is false', () => { - div.call(drawTimeSeriesGraph, store, '12345678', false, false); - - expect(div.selectAll('.tooltip-text-group').size()).toBe(0); - expect(div.selectAll('.focus-overlay').size()).toBe(0); - expect(div.selectAll('.focus-circle').size()).toBe(0); - expect(div.selectAll('.focus-line').size()).toBe(0); - }); - - it('Should render the tooltip if showTooltip is true', () => { - div.call(drawTimeSeriesGraph, store, '12345678', false, true); + it('Should render median data if visible', () => { + store.dispatch(setMedianDataVisibility(true)); + drawTimeSeriesGraph(div, store, '11112222', 'USGS', 'This site', false, false); - expect(div.selectAll('.tooltip-text-group').size()).toBe(1); - expect(div.selectAll('.focus-overlay').size()).toBe(1); - expect(div.selectAll('.focus-circle').size()).toBe(1); - expect(div.selectAll('.focus-line').size()).toBe(1); - }); + expect(div.selectAll('.ts-primary-group').size()).toBe(1); + expect(div.selectAll('.ts-compare-group').size()).toBe(0); + expect(div.selectAll('.iv-graph-gw-levels-group').size()).toBe(1); + expect(div.selectAll('.median-stats-group').size()).toBe(1); }); }); \ No newline at end of file diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/time-series-lines.js b/assets/src/scripts/monitoring-location/components/hydrograph/time-series-lines.js index 72fa358d7b7509bbf69d73bff4ca30d774956155..b8a4db8ab199410a72f5f5a8e8b6be8f76b0f389 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/time-series-lines.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/time-series-lines.js @@ -44,7 +44,7 @@ const drawMaskSegment = function(group, {segment, isCurrentMethod, dataKind, xSc const rectHeight = Math.abs(yRangeEnd - yRangeStart); const maskGroup = group.append('g') - .attr('class', 'dv-mask-group'); + .attr('class', 'iv-mask-group'); maskGroup.append('rect') .attr('x', xRangeStart) @@ -93,18 +93,18 @@ export const drawDataSegment = function(group, {segment, isCurrentMethod, dataKi export const drawDataSegments = function(elem, {visible, currentMethodID, tsSegmentsMap, dataKind, xScale, yScale, enableClip}, container) { container = container || elem.append('g'); - const elemId = `ts-${dataKind}-group`; + const elemClass = `ts-${dataKind}-group`; const isCurrentMethodID = function(thisMethodID) { return currentMethodID ? currentMethodID === thisMethodID : true; }; - container.selectAll(`#${elemId}`).remove(); + container.selectAll(`.${elemClass}`).remove(); if (!visible || !tsSegmentsMap) { return; } const tsLineGroup = container .append('g') - .attr('id', elemId); + .attr('class', elemClass); if (enableClip) { tsLineGroup.attr('clip-path', 'url(#graph-clip)'); diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/tooltip.js b/assets/src/scripts/monitoring-location/components/hydrograph/tooltip.js index dee1d255db132a23309285f218ecdaff72376b7d..58db946424a70687f1b2e7856c3c7bbf9392b50a 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/tooltip.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/tooltip.js @@ -11,7 +11,7 @@ import {getPrimaryParameter} from 'ml/selectors/hydrograph-data-selector'; import {getGraphCursorOffset} from 'ml/selectors/hydrograph-state-selector'; import {setGraphCursorOffset} from 'ml/store/hydrograph-state'; -import {getCursorTime, getIVDataCursorPoints, getIVDataTooltipPoints, getGroundwaterLevelCursorPoint, +import {getCursorTime, getIVDataCursorPoint, getIVDataTooltipPoint, getGroundwaterLevelCursorPoint, getGroundwaterLevelTooltipPoint } from './selectors/cursor'; import {getMainLayout} from './selectors/layout'; @@ -43,8 +43,8 @@ const getGWLevelTextInfo = function(point, unitCode) { }; const createTooltipTextGroup = function(elem, { - currentPoints, - comparePoints, + currentPoint, + comparePoint, gwLevelPoint, parameter, layout @@ -60,14 +60,14 @@ const createTooltipTextGroup = function(elem, { .call(adjustMarginOfTooltips); } const unitCode = parameter ? parameter.unit : ''; - const currentTooltipData = Object.values(currentPoints).map((tsPoint) => { - return getIVDataTooltipTextInfo(tsPoint, 'primary', unitCode); - }); - const compareTooltipData = Object.values(comparePoints).map((tsPoint) => { - return getIVDataTooltipTextInfo(tsPoint, 'compare', unitCode); - }); - - let tooltipTextData = currentTooltipData.concat(compareTooltipData); + + let tooltipTextData = []; + if (currentPoint) { + tooltipTextData.push(getIVDataTooltipTextInfo(currentPoint, 'primary', unitCode)); + } + if(comparePoint) { + tooltipTextData.push(getIVDataTooltipTextInfo(comparePoint, 'compare', unitCode)); + } if (gwLevelPoint) { tooltipTextData.push(getGWLevelTextInfo(gwLevelPoint, unitCode)); } @@ -100,8 +100,8 @@ const createTooltipTextGroup = function(elem, { */ export const drawTooltipText = function(elem, store) { elem.call(link(store, createTooltipTextGroup, createStructuredSelector({ - currentPoints: getIVDataCursorPoints('primary', 'current'), - comparePoints: getIVDataCursorPoints('compare', 'prioryear'), + currentPoint: getIVDataCursorPoint('primary', 'current'), + comparePoint: getIVDataCursorPoint('compare', 'prioryear'), gwLevelPoint: getGroundwaterLevelCursorPoint, parameter: getPrimaryParameter, layout: getMainLayout @@ -122,11 +122,17 @@ export const drawTooltipFocus = function(elem, store) { }))); elem.call(link(store, drawFocusCircles, createSelector( - getIVDataTooltipPoints('primary', 'current'), - getIVDataTooltipPoints('compare', 'prioryear'), + getIVDataTooltipPoint('primary', 'current'), + getIVDataTooltipPoint('compare', 'prioryear'), getGroundwaterLevelTooltipPoint, (current, compare, gwLevel) => { - let points = current.concat(compare); + let points = []; + if (current) { + points.push(current); + } + if (compare) { + points.push(compare); + } if (gwLevel) { points.push(gwLevel); } diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/tooltip.test.js b/assets/src/scripts/monitoring-location/components/hydrograph/tooltip.test.js index 037d9c18f20ee59576c99844c1356310cab1ef23..66eff7afcd7216c157c92897bc66d821c35d5f78 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/tooltip.test.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/tooltip.test.js @@ -1,199 +1,38 @@ import {select} from 'd3-selection'; +import config from 'ui/config'; import * as utils from 'ui/utils'; import {configureStore} from 'ml/store'; -import {Actions} from 'ml/store/instantaneous-value-time-series-state'; import {drawTooltipText, drawTooltipFocus, drawTooltipCursorSlider} from './tooltip'; +import {TEST_GW_LEVELS, TEST_PRIMARY_IV_DATA, TEST_CURRENT_TIME_RANGE} from "./mock-hydrograph-state"; describe('monitoring-location/components/hydrograph/tooltip module', () => { utils.mediaQuery = jest.fn().mockReturnValue(true); - - let data = [12, 13, 14, 15, 16].map(hour => { - return { - dateTime: new Date(`2018-01-03T${hour}:00:00.000Z`).getTime(), - qualifiers: ['P'], - value: hour - }; - }); - const maskedData = [ - { - dateTime: new Date('2018-01-03T17:00:00.000Z').getTime(), - qualifiers: ['Fld', 'P'], - value: null - }, - { - dateTime: new Date('2018-01-03T18:00:00.000Z').getTime(), - qualifiers: ['Mnt', 'P'], - value: null - } - - ]; - data = data.concat(maskedData); - - const testState = { - ianaTimeZone: 'America/Chicago', - ivTimeSeriesData: { - timeSeries: { - '69928:current:P7D': { - points: data, - tsKey: 'current:P7D', - variable: '00060id', - methodID: 69928 - }, - '69928:compare:P7D': { - points: data, - tsKey: 'compare:P7D', - variable: '00060id', - methodID: 69928 - }, - '69929:current:P7D': { - points: data, - tsKey: 'current:P7D', - variable: '00010id', - methodID: 69929 - }, - '69929:compare:P7D': { - points: data, - tsKey: 'compare:P7D', - variable: '00010id', - methodID: 69929 - } - }, - timeSeriesCollections: { - 'current:P7D': { - variable: '00060id', - timeSeries: ['00060:current'] - }, - 'compare:P7D': { - variable: '00060id', - timeSeries: ['00060:compare'] - } - }, - methods: { - 69928: { - methodDescription: '', - methodID: 69928 - }, - 69929: { - methodDescription: '', - methodID: 69929 - } - }, - variables: { - '00060id': { - oid: '00060id', - variableCode: { - value: '00060' - }, - unit: { - unitCode: 'ft3/s' - } - }, - '00010id': { - oid: '00010id', - variableCode: { - value: '00010' - }, - unit: { - unitCode: 'deg C' - } - } - }, - requests: { - 'current:P7D': { - timeSeriesCollections: ['current'] - }, - 'compare:P7D': { - timeSeriesCollections: ['compare'] - } - }, - queryInfo: { - 'current:P7D': { - notes: { - 'filter:timeRange': { - mode: 'RANGE', - interval: { - start: new Date('2018-01-03T12:00:00.000Z').getTime(), - end: new Date('2018-01-04T16:00:00.000Z').getTime() - } - } - } - }, - 'compare:P7D': { - notes: { - 'filter:timeRange': { - mode: 'RANGE', - interval: { - start: new Date('2018-01-03T12:00:00.000Z').getTime(), - end: new Date('2018-01-03T16:00:00.000Z').getTime() - } - } - } - } - }, - qualifiers: { - 'P': { - qualifierCode: 'P', - qualifierDescription: 'Provisional data subject to revision.', - qualifierID: 0, - network: 'NWIS', - vocabulary: 'uv_rmk_cd' - }, - 'Fld': { - qualifierCode: 'Fld', - qualifierDescription: 'Flood', - qualifierId: 0, - network: 'NWIS', - vocabulary: 'uv_rmk_cd' - }, - 'Mnt': { - qualifierCode: 'Mnt', - qualifierDescription: 'Maintenance', - qualifierId: 0, - network: 'NWIS', - vocabulary: 'uv_rmk_cd' - } - } + config.locationTimeZone = 'America/Chicago'; + const TEST_STATE = { + hydrographData: { + primaryIVData: TEST_PRIMARY_IV_DATA, + groundwaterLevels: TEST_GW_LEVELS, + currentTimeRange: TEST_CURRENT_TIME_RANGE }, - ivTimeSeriesState: { - showIVTimeSeries: { - current: true, - compare: true - }, - currentIVVariableID: '00060id', - currentIVMethodID: 69928, - currentIVDateRange: 'P7D', - customIVTimeRange: null, - ivGraphCursorOffset: 61200000 + hydrographState: { + selectedIVMethodID: '90649', + graphCursorOffset: 500000 }, ui: { windowWidth: 1300, width: 990 - }, - discreteData: { - groundwaterLevels: { - '00060id': { - variables: { - oid: '00060id', - variableCode: { - value: '00060' - }, - unit: { - unitCode: 'ft3/s' - } - }, - values: data - } - } } }; describe('drawTooltipText', () => { let div; + let store; beforeEach(() => { div = select('body').append('div'); + store = configureStore(TEST_STATE); }); afterEach(() => { @@ -201,15 +40,6 @@ describe('monitoring-location/components/hydrograph/tooltip module', () => { }); it('Creates the container for tooltips', () => { - let store = configureStore({ - ivTimeSeriesState: { - ivGraphCursorOffset: null, - showIVTimeSeries: { - current: true - } - } - }); - div.call(drawTooltipText, store); const textGroup = div.selectAll('.tooltip-text-group'); @@ -217,113 +47,20 @@ describe('monitoring-location/components/hydrograph/tooltip module', () => { }); it('Creates the text elements with the label for the focus times', () => { - let store = configureStore(Object.assign({}, testState, { - ivTimeSeriesState: Object.assign({}, testState.ivTimeSeriesState, { - ivGraphCursorOffset: 2 * 60 * 60 * 1000 - }) - })); - div.call(drawTooltipText, store); + let value = div.select('.primary-tooltip-text').text().split(' - ')[0]; + expect(value).toBe('24.1 ft'); - let value = div.select('.current-tooltip-text').text().split(' - ')[0]; - expect(value).toBe('14 ft3/s'); - value = div.select('.compare-tooltip-text').text().split(' - ')[0]; - expect(value).toBe('14 ft3/s'); - value = div.select('.gwlevel-tooltip-text').text().split(' - ')[0]; - expect(value).toBe('14 ft3/s'); - }); - - it('Text contents are updated when the store is provided with new focus times', () => { - let store = configureStore(Object.assign({}, testState, { - ivTimeSeriesState: Object.assign({}, testState.ivTimeSeriesState, { - ivGraphCursorOffset: 1 - }) - })); - - div.call(drawTooltipText, store); - - let value = div.select('.current-tooltip-text').text().split(' - ')[0]; - expect(value).toBe('12 ft3/s'); - store.dispatch(Actions.setIVGraphCursorOffset(3 * 60 * 60 * 1000)); - - return new Promise(resolve => { - window.requestAnimationFrame(() => { - value = div.select('.current-tooltip-text').text().split(' - ')[0]; - expect(value).toBe('15 ft3/s'); - - value = div.select('.compare-tooltip-text').text().split(' - ')[0]; - expect(value).toBe('15 ft3/s'); - - value = div.select('.gwlevel-tooltip-text').text().split(' - ')[0]; - expect(value).toBe('15 ft3/s'); - resolve(); - }); - }); - }); + expect(div.select('.compare-tooltip-text').size()).toBe(0); - it('Shows the qualifier text if focus is near masked data points', () => { - let store = configureStore(Object.assign({}, testState, { - ivTimeSeriesState: Object.assign({}, testState.ivTimeSeriesState, { - ivGraphCursorOffset: 1 - }) - })); - - div.call(drawTooltipText, store); - store.dispatch(Actions.setIVGraphCursorOffset(299 * 60 * 1000)); // 2018-01-03T16:59:00.000Z - - return new Promise(resolve => { - window.requestAnimationFrame(() => { - expect(div.select('.current-tooltip-text').text()).toContain('Flood'); - resolve(); - }); - }); - }); - - it('Creates the correct text for values of zero', () => { - const zeroData = [12, 13, 14, 15, 16].map(hour => { - return { - dateTime: new Date(`2018-01-03T${hour}:00:00.000Z`).getTime(), - qualifiers: ['P'], - value: 0 - }; - }); - let store = configureStore(Object.assign({}, testState, { - ivTimeSeriesData: Object.assign({}, testState.ivTimeSeriesData, { - timeSeries: Object.assign({}, testState.ivTimeSeriesData.timeSeries, { - '69928:current:P7D': Object.assign({}, testState.ivTimeSeriesData.timeSeries['69928:current:P7D'], { - points: zeroData - }) - }) - }), - ivTimeSeriesState: Object.assign({}, testState.ivTimeSeriesState, { - ivGraphCursorOffset: 10 - }) - })); - div.call(drawTooltipText, store); - store.dispatch(Actions.setIVGraphCursorOffset(119 * 60 * 1000)); - return new Promise(resolve => { - window.requestAnimationFrame(() => { - let value = div.select('.current-tooltip-text').text().split(' - ')[0]; - - expect(value).toBe('0 ft3/s'); - resolve(); - }); - }); + value = div.select('.gwlevel-tooltip-text').text().split(' - ')[0]; + expect(value).toBe('27.2 ft'); }); }); - describe('createTooltipFocus', () => { let svg, currentTsData, compareTsData; beforeEach(() => { svg = select('body').append('svg'); - - currentTsData = data; - compareTsData = [12, 13, 14, 15, 16].map(hour => { - return { - dateTime: new Date(`2017-01-03T${hour}:00:00.000Z`).getTime(), - value: hour + 1 - }; - }); }); afterEach(() => { @@ -331,57 +68,7 @@ describe('monitoring-location/components/hydrograph/tooltip module', () => { }); it('Creates focus lines and focus circles when cursor not set', () => { - let store = configureStore(Object.assign({}, testState, { - ivTimeSeriesData: Object.assign({}, testState.ivTimeSeriesData, { - timeSeries: Object.assign({}, testState.ivTimeSeriesData.timeSeries, { - '69928:current:P7D': { - points: currentTsData, - tsKey: 'current:P7D', - variable: '00060id', - methodID: 69928 - }, - '69928:compare:P7D': { - points: compareTsData, - tsKey: 'compare:P7D', - variable: '00060id', - methodID: 69928 - } - }) - }), - ivTimeSeriesState: Object.assign({}, testState.ivTimeSeriesState, { - ivGraphCursorOffset: 3 * 60 * 60 * 1000 - }) - })); - - svg.call(drawTooltipFocus, store); - - expect(svg.selectAll('.focus-line').size()).toBe(1); - expect(svg.selectAll('.focus-circle').size()).toBe(2); - expect(svg.select('.focus-overlay').size()).toBe(1); - }); - - it('Focus circles and line are displayed if cursor is set', () => { - let store = configureStore(Object.assign({}, testState, { - ivTimeSeriesData: Object.assign({}, testState.ivTimeSeriesData, { - timeSeries: Object.assign({}, testState.ivTimeSeriesData.timeSeries, { - '69928:current:P7D': { - points: currentTsData, - tsKey: 'current:P7D', - variable: '00060id', - methodID: 69928 - }, - '69928:compare:P7D': { - points: compareTsData, - tsKey: 'compare:P7D', - variable: '00060id', - methodID: 69928 - } - }) - }), - ivTimeSeriesState: Object.assign({}, testState.ivTimeSeriesState, { - ivGraphCursorOffset: 3 * 60 * 60 * 1000 - }) - })); + let store = configureStore(TEST_STATE); svg.call(drawTooltipFocus, store); @@ -402,7 +89,7 @@ describe('monitoring-location/components/hydrograph/tooltip module', () => { }); it('should render the cursor slider', () => { - let store = configureStore(testState); + let store = configureStore(TEST_STATE); drawTooltipCursorSlider(div, store); const sliderSvg = div.selectAll('.cursor-slider-svg'); diff --git a/assets/src/scripts/monitoring-location/iv-data-utils.js b/assets/src/scripts/monitoring-location/iv-data-utils.js index 435adfe9d2d34658ae346a172108363dbf9992df..840980b28828ed5c1db012948a89ae8973e3c523 100644 --- a/assets/src/scripts/monitoring-location/iv-data-utils.js +++ b/assets/src/scripts/monitoring-location/iv-data-utils.js @@ -1,69 +1,6 @@ -import merge from 'lodash/merge'; import config from 'ui/config'; -import {convertCelsiusToFahrenheit} from 'ui/utils'; - -/* -* Helper function that determines if URL query parameter for time period is -* acceptable. The purpose here is to prevent users from altering the URL in a way that -* is undesirable or impossible for the application to process. We will accept -* time periods in the form of P{MAX_DIGITS_FOR_DAYS_FROM_TODAY}D, or P1Y -* @param {String} periodCode the time period code from the timespan radio buttons -* @return {Boolean} if the value is within acceptable limits -* */ -export const isPeriodWithinAcceptableRange = function(periodCode) { - - return periodCode && - periodCode.charAt(0) === 'P' && - periodCode.slice(-1) === 'D' && - periodCode.slice(1,-1).length < config.MAX_DIGITS_FOR_DAYS_FROM_TODAY || - periodCode === 'P1Y'; -}; - - -/* -* Helper function that sorts between 'custom' (user defined) and the default time period options -* @param {String} periodCode the time period code from the timespan radio buttons -* @return {Boolean} if the value is or is not a 'custom' (user defined) time period -* */ -export const isPeriodCustom = function(periodCode) { - - return periodCode !== 'P7D' && - periodCode !== 'P30D' && - periodCode !== 'P1Y'; -}; - - -/* -* Helper function that sorts between 'custom' (user defined) and the default time period options -* There are two general categories of user selected time periods (noted as 'period' in the code) for display on the hydrograph. -* The first category contains time periods that are pre-defined in the application like 7 days, 30 days, and one year. -* The second category are 'custom' time periods defined by the user through use of a calender date picker or days in an input field. -* The 'period' information is passed in the URL as a parameter (such as &period=P1D). In the following line the period is -* parsed into a 'custom' (or not) category and then parts are used to set the checked radio buttons and form fields as needed -* @param {String} the time period code from the timespan radio buttons -* @return {Object} parsed period code values -* */ -export const parsePeriodCode = function(periodCode) { - if (periodCode) { - const mainTimeRangeSelectionButton = isPeriodCustom(periodCode) ? 'custom' : periodCode; - const userInputNumberOfDays = mainTimeRangeSelectionButton === 'custom' ? periodCode.slice(1,-1) : ''; - - return { - 'mainTimeRangeSelectionButton' : mainTimeRangeSelectionButton, - 'numberOfDaysFieldValue': userInputNumberOfDays - }; - } else { - - return { - 'mainTimeRangeSelectionButton' : 'P7D', - 'numberOfDaysFieldValue': '' - }; - } -}; - - /* * Avoids having two Fahrenheit parameters for monitoring locations that already have both a measured Celsius and * Fahrenheit value for corresponding data parameters. @@ -71,41 +8,44 @@ export const parsePeriodCode = function(periodCode) { * '00020': '00021' - air temperature C:F * '00010': '00011' - water temperature C:F * '45589': '45590' - Temperature, internal, within equipment shelter C:F -* @params {String} parameterCode - five digit USGS identifier for data sampled at monitoring location -* @params {Object} - NWISVariable - contains information about the current NWIS 'variables' in the application state -* @returns {Boolean} - isParameterMatching, true if current parameter code matches a corresponding code for +* @param {String} parameterCode - five digit USGS identifier for data sampled at monitoring location +* @param {Array of String} - allParameterCodes +* @return {Boolean} - isParameterMatching, true if current parameter code matches a corresponding code for * Fahrenheit in the application state, false if it does not. */ -const checkForMeasuredFahrenheitParameters = function(parameterCode, NWISVariables) { +export const hasMeasuredFahrenheitParameter = function(parameterCode, allParameterCodes) { let isParameterMatching; - const allVariableParameterCodes = Object.entries(NWISVariables).map(variable => variable[1].variableCode.value); - // If the current parameter code is one that could match a measured Fahrenheit parameter, cross reference the - // parameter with the known corresponding codes that may match a value in the application state. - if (config.CELSIUS_CODES_WITH_FAHRENHEIT_COUNTERPARTS.includes(parameterCode)) { - switch(parameterCode) { - case '00020': - isParameterMatching = allVariableParameterCodes.includes('00021'); - break; - case '00010': - isParameterMatching = allVariableParameterCodes.includes('00011'); - break; - case '45589': - isParameterMatching = allVariableParameterCodes.includes('45590'); - break; - default: - isParameterMatching = false; - } - } else { - isParameterMatching = false; + switch(parameterCode) { + case '00020': + isParameterMatching = allParameterCodes.includes('00021'); + break; + case '00010': + isParameterMatching = allParameterCodes.includes('00011'); + break; + case '45589': + isParameterMatching = allParameterCodes.includes('45590'); + break; + default: + isParameterMatching = false; } return isParameterMatching; }; +/* + * Return true if this parameter code represents one that is calculated + * @param {String} parameterCode + * @return {Boolean} + */ export const isCalculatedTemperature = function(parameterCode) { return parameterCode.slice(-1) === config.CALCULATED_TEMPERATURE_VARIABLE_CODE; }; +/* + * Converts the parameter object for a celsius parameter to one that represents calculated Fahrenheit. + * @param {Object} parameter + * @return {Object} converted parameter + */ export const getConvertedTemperatureParameter = function(parameter) { return { ...parameter, @@ -115,94 +55,3 @@ export const getConvertedTemperatureParameter = function(parameter) { unit: parameter.unit.replace('C', 'F') }; }; -/* -* Converts a Celsius 'variable' from the NWIS system to one we can use to show Fahrenheit. -* @param {Object} NWISVariable - Has various properties related to descriptions of data -* @return {Object} NWIS variable converted for use with Fahrenheit - */ -const getConvertedVariable = function(celsiusVariable) { - const calculatedFahrenheitVariable = { - ...celsiusVariable, - note: [...celsiusVariable.note], - options: [...celsiusVariable.options], - variableProperty: [...celsiusVariable.variableProperty], - variableName: celsiusVariable.variableName.replace('C', 'F (calculated)'), - variableDescription: celsiusVariable.variableDescription.replace('Celsius', 'Fahrenheit (calculated)'), - unit: { - ...celsiusVariable.unit, - unitCode: celsiusVariable.unit.unitCode.replace('C', 'F') - }, - variableCode: { - ...celsiusVariable.variableCode, - value: `${celsiusVariable.variableCode.value}${config.CALCULATED_TEMPERATURE_VARIABLE_CODE}`, - variableID: `${celsiusVariable.variableCode.variableID}${config.CALCULATED_TEMPERATURE_VARIABLE_CODE}` - }, - oid: `${celsiusVariable.oid}${config.CALCULATED_TEMPERATURE_VARIABLE_CODE}` - }; - return { - [calculatedFahrenheitVariable.oid]: calculatedFahrenheitVariable - }; -}; - -/* -* Converts the temperature values (points) in the cloned time series from Celsius to Fahrenheit -* @param {Object} timeSeries - a time series with Celsius data values -* @param {Object} tsRequestKey - the time series request key -* @param (Object} parameterCode - parameter code (should always be a Celsius code) -* @return {Object} A time series with points converted from Celsius to Fahrenheit -* important properties (there are many others) -* @prop - {Object) points - An object containing temperature data -* @prop - {String} variable - The 'variable' from the NWIS system, will have a suffix indicating a converted value - */ -const getConvertedTimeSeries = function(timeSeries, tsRequestKey, parameterCode) { - const fahrenheitTimeSeries = { - ...timeSeries, - censorCode: [...timeSeries.censorCode], - offset: [...timeSeries.offset], - qualifier: [...timeSeries.qualifier], - qualityControlLevel: [...timeSeries.qualityControlLevel], - sample: [...timeSeries.sample], - source: [...timeSeries.source], - points: timeSeries.points.map((point) => { - return { - ...point, - qualifiers: [...point.qualifiers], - value: point.value === '' || isNaN(point.value) ? - null : convertCelsiusToFahrenheit(point.value).toFixed(2) - }; - }), - variable: `${timeSeries.variable}${config.CALCULATED_TEMPERATURE_VARIABLE_CODE}` - }; - const fahrenheitTsRequestKey = `${tsRequestKey}:${parameterCode}${config.CALCULATED_TEMPERATURE_VARIABLE_CODE}`; - return { - [fahrenheitTsRequestKey]: fahrenheitTimeSeries - }; -}; - -/* -* Takes a time series collection checks if there are any Celsius parameter codes and calls other methods to convert -* that series to a new object which is then added to the application state. -* @param {Object} collection - complex object built with information retrieved from NWIS - contains information about -* all data that appears in the hydrograph. NOTE: The contents of this parameter are mutated - */ -export const convertCelsiusCollectionsToFahrenheitAndMerge = function(collection) { - if ('timeSeries' in collection) { - Object.entries(collection.timeSeries).forEach(([tsRequestKey, timeSeriesDetails]) => { - const variableCode = timeSeriesDetails.variable; - // Cross reference the 'variableCode' in the 'timeSeries' with 'variables' in the state - // to get the corresponding parameter code - const parameterCode = collection.variables[variableCode].variableCode.value; - // For any Celsius parameter codes, clone the application state variables, 'variables and timeSeries' - // and convert the appropriate properties to Fahrenheit. Then add the cloned objects to the 'collection' - if (config.TEMPERATURE_PARAMETERS.celsius.includes(parameterCode)) { - if (!checkForMeasuredFahrenheitParameters(parameterCode, collection.variables)) { - const convertedTimeSeries = getConvertedTimeSeries(collection.timeSeries[tsRequestKey], tsRequestKey, parameterCode); - const convertedVariable = getConvertedVariable(collection.variables[variableCode]); - - merge(collection.variables, convertedVariable); - merge(collection.timeSeries, convertedTimeSeries); - } - } - }); - } -}; diff --git a/assets/src/scripts/monitoring-location/iv-data-utils.test.js b/assets/src/scripts/monitoring-location/iv-data-utils.test.js index 7a3e2126b7da9aee4689a8dda995f25530c24295..5db86539e13aef5f98692c464657f0ff35137fdf 100644 --- a/assets/src/scripts/monitoring-location/iv-data-utils.test.js +++ b/assets/src/scripts/monitoring-location/iv-data-utils.test.js @@ -1,332 +1,52 @@ -import { - convertCelsiusCollectionsToFahrenheitAndMerge, - isPeriodCustom, - isPeriodWithinAcceptableRange, - parsePeriodCode -} from './iv-data-utils'; -import {combineReducers, createStore} from 'redux'; - -import {ivTimeSeriesDataReducer} from 'ml/store/instantaneous-value-time-series-data'; - -describe('isPeriodWithinAcceptableRange', () => { - it('will return correct boolean value if the url parameters for time period a within an acceptable range', () => { - expect(isPeriodWithinAcceptableRange('totalNonsense')).toEqual(false); - expect(isPeriodWithinAcceptableRange('P700000D')).toEqual(false); - expect(isPeriodWithinAcceptableRange('P2Y')).toEqual(false); - expect(isPeriodWithinAcceptableRange('P1Y')).toEqual(true); - expect(isPeriodWithinAcceptableRange('P32D')).toEqual(true); - }); -}); -describe('isCustomPeriod', () => { - it('will return correct boolean value is period is custom', () => { - expect(isPeriodCustom('P7D')).toEqual(false); - expect(isPeriodCustom('P30D')).toEqual(false); - expect(isPeriodCustom('P1Y')).toEqual(false); - expect(isPeriodCustom('P32D')).toEqual(true); - }); -}); - -describe('parsePeriodCode', () => { - it('will break down the period code into input selection button and the number of days the user entered', () => { - expect(parsePeriodCode('P32D')) - .toEqual({mainTimeRangeSelectionButton: 'custom', numberOfDaysFieldValue: '32'}); - expect(parsePeriodCode('P1Y')) - .toEqual({mainTimeRangeSelectionButton: 'P1Y', numberOfDaysFieldValue: ''}); - expect(parsePeriodCode(null)) - .toEqual({'mainTimeRangeSelectionButton' : 'P7D', 'numberOfDaysFieldValue': ''}); - }); -}); +import {hasMeasuredFahrenheitParameter, isCalculatedTemperature, getConvertedTemperatureParameter +} from './iv-data-utils'; +describe('monitoring-location/iv-data-utils', () => { -describe('convertCelsiusCollectionsToFahrenheitAndMerge', () => { - let store; - const TEST_STATE = { - 'variables': { - '45807042': { - 'note': [], - 'options': [], - 'variableProperty': [], - 'variableCode': { - 'value': '00010', - 'variableID': 45807042 - }, - 'variableName': 'Temperature, water, °C', - 'variableDescription': 'Temperature, water, degrees Celsius', - 'unit': { - 'unitCode': 'deg C' - }, - 'oid': '45807042' - }, - '45807197': { - 'variableCode': { - 'value': '00060', - 'variableID': 45807197 - }, - 'variableName': 'Streamflow, ft³/s', - 'variableDescription': 'Discharge, cubic feet per second', - 'unit': { - 'unitCode': 'ft3/s' - }, - 'oid': '45807197' - }, - '45807202': { - 'variableCode': { - 'value': '00065', - 'variableID': 45807202 - }, - 'variableName': 'Gage height, ft', - 'variableDescription': 'Gage height, feet', - 'unit': { - 'unitCode': 'ft' - }, - 'oid': '45807202' - } - }, - 'timeSeries': { - '157775:current:P7D': { - 'variable': '45807042', - 'points': [ - { - 'value': 0, - 'qualifiers': [ - 'P' - ], - 'dateTime': 1609515000000 - }, - { - 'value': 'something wrong with this value', - 'qualifiers': [ - 'P' - ], - 'dateTime': 1609515900000 - }, - { - 'value': '', - 'qualifiers': [ - 'P' - ], - 'dateTime': 1609516800000 - }, - { - 'value': 0.01, - 'qualifiers': [ - 'P' - ], - 'dateTime': 1609517700000 - }, - { - 'value': -3.1, - 'qualifiers': [ - 'P' - ], - 'dateTime': 1609518600000 - }, - { - 'value': null, - 'qualifiers': [ - 'P', 'Eqp' - ], - 'dateTime': 1609519500000 - }, - { - 'value': 3.1, - 'qualifiers': [ - 'P' - ], - 'dateTime': 1609520400000 - } - ] - }, - '157776:current:P7D': { - 'variable': '45807197' - }, - '237348:current:P7D': { - 'variable': '45807202' - } - }, - 'timeSeriesCollections': { - 'USGS:05370000:00010:00000:current:P7D': { - 'variable': '45807042', - 'name': 'USGS:05370000:00010:00000', - 'timeSeries': [ - '157775:current:P7D' - ] - }, - 'USGS:05370000:00060:00000:current:P7D': { - 'variable': '45807197', - 'name': 'USGS:05370000:00060:00000', - 'timeSeries': [ - '157776:current:P7D' - ] - }, - 'USGS:05370000:00065:00000:current:P7D': { - 'variable': '45807202', - 'name': 'USGS:05370000:00065:00000', - 'timeSeries': [ - '237348:current:P7D' - ] - } - } - }; + describe('hasMeasuredFahrenheitParameter', () => { + const allParameterCodes = ['72019', '00020', '00021', '00010', '45589']; - beforeEach(() => { - store = createStore( - combineReducers({ - ivTimeSeriesData: ivTimeSeriesDataReducer - }), - { - ivTimeSeriesData: { - ...TEST_STATE - } - } - ); - }); + it('Return false if the code does not have a fahrenheit counterpart', () => { + expect(hasMeasuredFahrenheitParameter('72019', allParameterCodes)).toBe(false); + }); - const convertedVariableValue = - { - 'note': [], - 'options': [], - 'variableProperty': [], - 'oid': '45807042F', - 'unit': {'unitCode': 'deg F'}, - 'variableCode': {'value': '00010F', 'variableID': '45807042F'}, - 'variableDescription': 'Temperature, water, degrees Fahrenheit (calculated)', - 'variableName': 'Temperature, water, °F (calculated)' - }; - const convertedPoints = [ - { - 'dateTime': 1609515000000, - 'qualifiers': [ - 'P' - ], - 'value': '32.00' - }, - { - 'dateTime': 1609515900000, - 'qualifiers': [ - 'P' - ], - 'value': null - }, - { - 'dateTime': 1609516800000, - 'qualifiers': [ - 'P' - ], - 'value': null - }, - { - 'dateTime': 1609517700000, - 'qualifiers': [ - 'P' - ], - 'value': '32.02' - }, - { - 'dateTime': 1609518600000, - 'qualifiers': [ - 'P' - ], - 'value': '26.42' - }, - { - 'dateTime': 1609519500000, - 'qualifiers': [ - 'P', 'Eqp' - ], - 'value': '32.00' - }, - { - 'dateTime': 1609520400000, - 'qualifiers': [ - 'P' - ], - 'value': '37.58' - } - ]; + it('Return true if the measured fahrenheit parameter is in the list', () => { + expect(hasMeasuredFahrenheitParameter('00020', allParameterCodes)).toBe(true); + }); - const TEST_COLLECTION_WITH_MEASURED_FAHRENHEIT = { - 'variables': { - '1': { - 'variableCode': { - 'value': '00010', - 'variableID': 1 - }, - 'variableName': 'Temperature, water, °C', - 'variableDescription': 'Temperature, water, degrees Celsius', - 'unit': { - 'unitCode': 'deg C' - }, - 'oid': '1' - }, - '2': { - 'variableCode': { - 'value': '00011', - 'variableID': 2 - }, - 'variableName': 'Temperature, water, °F', - 'variableDescription': 'Temperature, water, degrees Fahrenheit', - 'unit': { - 'unitCode': 'deg F' - }, - 'oid': '2' - } - }, - 'timeSeries': { - '157775:current:P7D': { - 'variable': '1' - }, - '157776:current:P7D': { - 'variable': '2' - } - } - }; + it('Return false if the measured fahrenheit parameter is not in the list', () => { + expect(hasMeasuredFahrenheitParameter('00010', allParameterCodes)).toBe(false); + expect(hasMeasuredFahrenheitParameter('45589', allParameterCodes)).toBe(false); - it('Will handle a collection without a time series', () => { - let emptyCollection = {}; - convertCelsiusCollectionsToFahrenheitAndMerge(emptyCollection); - expect(emptyCollection).toEqual({}); + }); }); - it('will create a new variables key with the correct suffix in application state', () => { - convertCelsiusCollectionsToFahrenheitAndMerge(TEST_STATE); - const keyWithsuffix = '45807042F'; - const resultingState = Object.keys(store.getState().ivTimeSeriesData.variables); - expect(resultingState.includes(keyWithsuffix)).toBeTruthy(); - }); - - it('will create a new variable with the correct properties in application state', () => { - convertCelsiusCollectionsToFahrenheitAndMerge(TEST_STATE); - const keyWithsuffix = '45807042F'; - const resultingState = store.getState().ivTimeSeriesData.variables[keyWithsuffix]; - expect(resultingState).toStrictEqual(convertedVariableValue); - }); - - it('adds only the expected number of variables to the collection', () => { - convertCelsiusCollectionsToFahrenheitAndMerge(TEST_STATE); - const resultingState = Object.keys(store.getState().ivTimeSeriesData.variables); - expect(resultingState).toHaveLength(4); - }); - - it('will create a new timeSeries key with the expected key name', () => { - convertCelsiusCollectionsToFahrenheitAndMerge(TEST_STATE); - const resultingState = Object.keys(store.getState().ivTimeSeriesData.timeSeries); - const timeSeriesKey = '157775:current:P7D:00010F'; - expect(resultingState.includes(timeSeriesKey)).toBeTruthy(); - }); + describe('isCalculatedTemperature', () => { + it('Return true if this a calculated temperature parameter code', () => { + expect(isCalculatedTemperature('00010F')).toBe(true); + }); - it('will create a new set of converted temperature points', () => { - convertCelsiusCollectionsToFahrenheitAndMerge(TEST_STATE); - const timeSeriesKey = '157775:current:P7D:00010F'; - const resultingState = store.getState().ivTimeSeriesData.timeSeries[timeSeriesKey].points; - expect(resultingState).toStrictEqual(convertedPoints); + it('Return false if this is not a calculated temperature parameter code', () => { + expect(isCalculatedTemperature('00011')).toBe(false); + }); }); - it('will not add new keys if a measured Fahrenheit parameter already exists', () => { - convertCelsiusCollectionsToFahrenheitAndMerge(TEST_COLLECTION_WITH_MEASURED_FAHRENHEIT); - const convertedTimeSeriesKey = '157775:current:P7D:00010F'; - const resultingState = store.getState().ivTimeSeriesData; - expect(convertedTimeSeriesKey in resultingState).not.toBeTruthy(); + describe('getConvertedTemperatureParameter', () => { + it('Expects to convert the temperature parameter object to a calculated parameter object', () => { + expect(getConvertedTemperatureParameter({ + parameterCode: '00010', + name: 'Temperature, water, C', + description: 'Temperature, water, degrees Celsius', + unit: 'deg C', + hasIVData: true + })).toEqual({ + parameterCode: '00010F', + name: 'Temperature, water, F (calculated)', + description: 'Temperature, water, degrees Fahrenheit (calculated)', + unit: 'deg F', + hasIVData: true + }); + }); }); }); diff --git a/assets/src/scripts/monitoring-location/selectors/discrete-data-selector.js b/assets/src/scripts/monitoring-location/selectors/discrete-data-selector.js deleted file mode 100644 index ec698802cba1008bd67dabdab63ab13e4882f150..0000000000000000000000000000000000000000 --- a/assets/src/scripts/monitoring-location/selectors/discrete-data-selector.js +++ /dev/null @@ -1,38 +0,0 @@ -import {createSelector} from 'reselect'; - -import {getCurrentVariableID} from './time-series-selector'; - -/* - * Returns selector function which returns all of the groundwater levels. - * @return {Function} - The function returns an Object containing all of the groundwater levels. The - * object's keys represent parameter codes. - */ -export const getAllGroundwaterLevels = - (state) => state.discreteData.groundwaterLevels ? state.discreteData.groundwaterLevels : null; - -/* - * Returns a selector function which returns an array of all variables that have - * groundwater levels - * @return {Function} The Function returns an array of the variable properties for each variable - * in groundwaterLevels. - */ -export const getAllGroundwaterLevelVariables = createSelector( - getAllGroundwaterLevels, - (gwLevels) => { - return gwLevels ? Object.values(gwLevels).map(level => level.variable) : []; - } -); - -/* - * Returns selector function which returns the groundwater levels for the current IV variable - * @return {Function} - The function returns an Object with the following properties: - * @prop {Object} variable - * @prop {Object} values - */ -export const getIVCurrentVariableGroundwaterLevels = createSelector( - getAllGroundwaterLevels, - getCurrentVariableID, - (gwLevels, variableID) => { - return gwLevels && variableID && gwLevels[variableID] ? gwLevels[variableID] : {}; - } -); diff --git a/assets/src/scripts/monitoring-location/selectors/discrete-data-selector.test.js b/assets/src/scripts/monitoring-location/selectors/discrete-data-selector.test.js deleted file mode 100644 index 807beeef83df16185a5297c40988326ef31dd514..0000000000000000000000000000000000000000 --- a/assets/src/scripts/monitoring-location/selectors/discrete-data-selector.test.js +++ /dev/null @@ -1,142 +0,0 @@ -import {getAllGroundwaterLevels, getIVCurrentVariableGroundwaterLevels} from './discrete-data-selector'; - -describe('monitoring-location/selectors/discrete-data-selector', () => { - describe('getAllGroundwaterLevels', () => { - it('Return null if no groundwater levels are available', () => { - expect(getAllGroundwaterLevels({ - discreteData: {} - })).toBeNull(); - expect(getAllGroundwaterLevels({ - discreteData: { - groundwaterLevels: null - } - })).toBeNull(); - }); - - it('Return all groundwater levels', () => { - const TEST_DATA = { - '45807042': { - variable: { - variableCode: { - value: '72019' - }, - oid: '45807042' - }, - values: [ - {value: '11.5', qualifiers: [], date: 1586354400000} - ] - }, - 55807042: { - variable: { - variableCode: { - value: '65433' - }, - oid: '55807042' - }, - values: [ - {value: '15.5', qualifiers: [], date: 1586354400000}, - {value: '24.3', qualifiers: [], date: 1588946400000} - ] - } - }; - expect(getAllGroundwaterLevels({ - discreteData: { - groundwaterLevels: TEST_DATA - } - })).toEqual(TEST_DATA); - }); - }); - - describe('getIVCurrentVariableGroundwaterLevels', () => { - const TEST_IV_TIME_SERIES_DATA = { - ivTimeSeriesData: { - variables: { - '45807042': { - variableCode: { - 'value': '72019' - } - }, - '45807142': { - variableCode: { - 'value': '00010' - } - } - } - } - }; - const TEST_IV_TIME_SERIES_STATE = { - ivTimeSeriesState: { - currentIVVariableID: '45807042' - } - }; - - const TEST_GROUNDWATER_LEVELS = { - discreteData : { - groundwaterLevels : { - '45807042': { - variable: { - variableCode: { - value: '72019' - }, - oid: '45807042' - }, - values: [ - { - value: '34.5', - qualifiers: [], - dateTime: 1604775600000 - }, - { - value: '40.2', - qualifiers: [], - datetime: 1607972400000 - } - ] - } - } - } - }; - - it('Return empty object if no groundwater levels and current IV variable is not defined', () => { - expect(getIVCurrentVariableGroundwaterLevels({ - discreteData: {groundwaterLevels: null}, - ivTimeSeriesState: {}, - ivTimeSeriesData: {} - })).toEqual({}); - }); - - it('Return empty object if no groundwater levels but current IV variable is defined', () => { - expect(getIVCurrentVariableGroundwaterLevels({ - discreteData: {groundwaterLevels: null}, - ...TEST_IV_TIME_SERIES_DATA, - ...TEST_IV_TIME_SERIES_STATE - })).toEqual({}); - }); - - it('Return empty object if groundwater levels exist but no current IV variable is defined', () => { - expect(getIVCurrentVariableGroundwaterLevels({ - ivTimeSeriesState: {}, - ...TEST_IV_TIME_SERIES_DATA, - ...TEST_GROUNDWATER_LEVELS - })).toEqual({}); - }); - - it('Should return an empty object if no groundwater levels exist for selected IV variable', () => { - expect(getIVCurrentVariableGroundwaterLevels({ - ivTimeSeriesState: { - currentIVVariableID: '45807142' - }, - ...TEST_IV_TIME_SERIES_DATA, - ...TEST_GROUNDWATER_LEVELS - })).toEqual({}); - }); - - it('Should return the groundwater levels for selected IV variable', () => { - expect(getIVCurrentVariableGroundwaterLevels({ - ...TEST_IV_TIME_SERIES_DATA, - ...TEST_IV_TIME_SERIES_STATE, - ...TEST_GROUNDWATER_LEVELS - })).toEqual(TEST_GROUNDWATER_LEVELS.discreteData.groundwaterLevels['45807042']); - }); - }); -}); \ No newline at end of file diff --git a/assets/src/scripts/monitoring-location/selectors/flood-data-selector.test.js b/assets/src/scripts/monitoring-location/selectors/flood-data-selector.test.js index c1e663c66b974bac108d9c88a3b7a8a16510656a..2450acb4c29c86168e6f6df51945d5914d3b0460 100644 --- a/assets/src/scripts/monitoring-location/selectors/flood-data-selector.test.js +++ b/assets/src/scripts/monitoring-location/selectors/flood-data-selector.test.js @@ -131,15 +131,8 @@ describe('monitoring-location/selectors/flood-data-selector', () => { major_flood_stage: '26' } }, - ivTimeSeriesState: { - currentIVVariableID: '45807197' - }, - ivTimeSeriesData: { - variables: { - '45807197': { - variableCode: {value: '00060'} - } - } + hydrographState: { + selectedParameterCode: '00060' } })).toBeFalsy(); }); @@ -149,15 +142,8 @@ describe('monitoring-location/selectors/flood-data-selector', () => { floodData: { floodLevels: null }, - ivTimeSeriesState: { - currentIVVariableID: '45807197' - }, - ivTimeSeriesData: { - variables: { - '45807197': { - variableCode: {value: '00065'} - } - } + hydrographState: { + selectedParameterCode: '00065' } })).toBeFalsy(); }); @@ -173,15 +159,8 @@ describe('monitoring-location/selectors/flood-data-selector', () => { major_flood_stage: '26' } }, - ivTimeSeriesState: { - currentIVVariableID: '45807197' - }, - ivTimeSeriesData: { - variables: { - '45807197': { - variableCode: {value: '00065'} - } - } + hydrographState: { + selectedParameterCode: '00065' } })).toBeTruthy(); }); diff --git a/assets/src/scripts/monitoring-location/selectors/hydrograph-data-selector.js b/assets/src/scripts/monitoring-location/selectors/hydrograph-data-selector.js index 2adfc8df1f1f91a37cf8bd9a4c9d4eee8c00ffe3..214242c79a272f711fb189a9216aa630b94b78d3 100644 --- a/assets/src/scripts/monitoring-location/selectors/hydrograph-data-selector.js +++ b/assets/src/scripts/monitoring-location/selectors/hydrograph-data-selector.js @@ -6,14 +6,37 @@ import {createSelector} from 'reselect'; import config from 'ui/config'; +/* + * Returns a selector function which returns the time range for the timeRangeKind. + * @param {String} timeRangeKind - 'current' or 'prioryear' + * @return {Function} + * */ export const getTimeRange = memoize((timeRangeKind) => state => state.hydrographData[`${timeRangeKind}TimeRange`] || null); -export const getIVData = memoize((dataKind) => state => state.hydrographData[`${dataKind}IVData`]) || null; +/* + *Returns a selector function which returns the IV data for the dataKind + * @param {String} dataKind - 'primary' or 'compare' + * @return {Function} + */ +export const getIVData = memoize((dataKind) => state => state.hydrographData[`${dataKind}IVData`] || null); +/* + * Returns a selector function which returns the median statistics data + * @return {Function} + */ export const getMedianStatisticsData = state => state.hydrographData.medianStatisticsData || null; +/* + * Returns a selector function which returns the groundwater levels data + * @return {Function} + */ export const getGroundwaterLevels = state => state.hydrographData.groundwaterLevels || null; +/* + * Returns a selector which returns an Array [min, max] where min and max represent the value range for the IV data of dataKind + * @param {String} dataKind - 'primary' or 'compare' + * @return {Function} + */ export const getIVValueRange = memoize(dataKind => createSelector( getIVData(dataKind), ivData => { @@ -33,6 +56,11 @@ export const getIVValueRange = memoize(dataKind => createSelector( } )); +/* + * Returns a selector which returns an Array [min, max] representing the value range of the groundwater + * levels data + * @return {Function} + */ export const getGroundwaterLevelsValueRange = createSelector( getGroundwaterLevels, gwLevels => { @@ -47,6 +75,11 @@ export const getGroundwaterLevelsValueRange = createSelector( } ); +/* + * Returns a selector function which returns the parameter that the hydrographData is representing + * an is an Object containing the parameter code, name, description, and unit + * @return {Function} + */ export const getPrimaryParameter = createSelector( getIVData('primary'), getGroundwaterLevels, @@ -61,6 +94,10 @@ export const getPrimaryParameter = createSelector( } ); +/* + * Returns a selector function which returns an array of methods that have IV time series + * @return {Function} + */ export const getPrimaryMethods = createSelector( getIVData('primary'), ivData => { @@ -74,9 +111,11 @@ export const getPrimaryMethods = createSelector( /* * @return {Function} which returns an Object with keys by tsId. Each property * is an {Object} as follows: - * @prop {Array of Object} values - where each object has point, dateTime keys - * @prop {String} description - statistic description - * the value at local time for the month/day in the original statistics data + * @prop {Array of Object} values - where each object has point, dateTime keys + * @prop {String} description - statistic description + * @prop {String} beginYear + * @prop {String} endYear + * the value at local time for the month/day in the original statistics data */ export const getPrimaryMedianStatisticsData = createSelector( getTimeRange('current'), @@ -118,6 +157,10 @@ export const getPrimaryMedianStatisticsData = createSelector( } ); +/* + * Returns a selector function which returns an Array [min, max] which represents the range + * of the median data that is being shown. + */ export const getPrimaryMedianStatisticsValueRange = createSelector( getPrimaryMedianStatisticsData, statsData => { diff --git a/assets/src/scripts/monitoring-location/selectors/hydrograph-data-selector.test.js b/assets/src/scripts/monitoring-location/selectors/hydrograph-data-selector.test.js new file mode 100644 index 0000000000000000000000000000000000000000..3dfa313e3d735fad06f0a8e43a45a06f1f2dcaad --- /dev/null +++ b/assets/src/scripts/monitoring-location/selectors/hydrograph-data-selector.test.js @@ -0,0 +1,278 @@ +import {getTimeRange, getIVData, getMedianStatisticsData, getGroundwaterLevels, getIVValueRange, + getGroundwaterLevelsValueRange, getPrimaryParameter, getPrimaryMethods, getPrimaryMedianStatisticsData, + getPrimaryMedianStatisticsValueRange +} from './hydrograph-data-selector'; + +describe('monitoring-location/selectors/hydrograph-data-selectors', () => { + + const TEST_PRIMARY_IV_DATA = { + parameter: { + parameterCode: '00060', + name: 'Streamflow ft3/s', + description: 'Discharge, cubic feet per second', + unit: 'ft3/s' + }, + values: { + '15776': { + points: [ + {value: 18.2, dateTime: 1613421000000}, + {value: 20.2, dateTime: 1613421900000}, + {value: 19.9, dateTime: 1613422800000}], + method: {methodID: '15776'} + } + } + }; + + const TEST_COMPARE_IV_DATA = { + parameter: { + parameterCode: '00060', + name: 'Streamflow ft3/s', + description: 'Discharge, cubic feet per second', + unit: 'ft3/s' + }, + values: { + '15776': { + points: [ + {value: 15.2, dateTime: 1581885000000}, + {value: 13.2, dateTime: 1581885900000}, + {value: 12.9, dateTime: 1581886800000}], + method: {methodID: '15776'} + } + } + }; + + const TEST_MEDIAN_DATA = { + '153885': [ + {month_nu: 2, day_nu: 1, p50_va: 16, ts_id: '153885', loc_web_ds: 'Method1', begin_yr: '2011', end_yr: '2020'}, + {month_nu: 2, day_nu: 2, p50_va: 16.2, ts_id: '153885', loc_web_ds: 'Method1', begin_yr: '2011', end_yr: '2020'}, + {month_nu: 2, day_nu: 3, p50_va: 15.9, ts_id: '153885', loc_web_ds: 'Method1', begin_yr: '2011', end_yr: '2020'}, + {month_nu: 2, day_nu: 4, p50_va: 16.3, ts_id: '153885', loc_web_ds: 'Method1', begin_yr: '2011', end_yr: '2020'}, + {month_nu: 2, day_nu: 4, p50_va: 16.4, ts_id: '153885', loc_web_ds: 'Method1', begin_yr: '2011', end_yr: '2020'}, + {month_nu: 2, day_nu: 4, p50_va: 16.5, ts_id: '153885', loc_web_ds: 'Method1', begin_yr: '2011', end_yr: '2020'} + ] + }; + + const TEST_GROUNDWATER_LEVELS = { + parameter: { + parameterCode: '72019', + name: 'Depth to water level, ft below land surface', + description: 'Depth to water level, feet below land surface', + unit: 'ft' + }, + values: [ + {value: 27.33, dateTime: 1613421000000}, + {value: 26.2, dateTime: 1613421900000}, + {value: 26.8, dateTime: 1613422800000} + ] + }; + + describe('getTimeRange', () => { + it('returns null if time range is not defined', () => { + expect(getTimeRange('current')({ + hydrographData: {} + })).toBeNull(); + }); + + it('returns the stored time range for the timeRangeKind', () => { + expect(getTimeRange('current')({ + hydrographData: { + currentTimeRange: { + start: 1613413736967, + end: 1614018536967 + }, + prioryearTimeRange: { + start: 1581877736967, + end: 1582482536967 + } + } + })).toEqual({ + start: 1613413736967, + end: 1614018536967 + }); + }); + }); + + describe('getIVData', () => { + it('returns null if no iv data of data kind is store', () => { + expect(getIVData('primary')({ + hydrographData: {} + })).toBeNull(); + }); + + it('Returns the selected IV Data', () => { + expect(getIVData('primary')({ + hydrographData: { + primaryIVData: TEST_PRIMARY_IV_DATA, + compareIVData: TEST_COMPARE_IV_DATA + } + })).toEqual(TEST_PRIMARY_IV_DATA); + expect(getIVData('compare')({ + hydrographData: { + primaryIVData: TEST_PRIMARY_IV_DATA, + compareIVData: TEST_COMPARE_IV_DATA + } + })).toEqual(TEST_COMPARE_IV_DATA); + }); + }); + + describe('getMedianStatistics', () => { + it('Returns null if no median data', () => { + expect(getMedianStatisticsData({ + hydrographData: {} + })).toBeNull(); + }); + + it('Returns the median data', () => { + expect(getMedianStatisticsData({ + hydrographData: { + medianStatisticsData: TEST_MEDIAN_DATA + } + })).toEqual(TEST_MEDIAN_DATA); + }); + }); + + describe('getGroundwaterLevels', () => { + it('Returns null if no groundwater level data', () => { + expect(getGroundwaterLevels({ + hydrographData: {} + })).toBeNull(); + }); + + it('Returns groundwater level data', () => { + expect(getGroundwaterLevels({ + hydrographData: { + groundwaterLevels: TEST_GROUNDWATER_LEVELS + } + })).toEqual(TEST_GROUNDWATER_LEVELS); + }); + }); + + describe('getIVValueRange', () => { + it('Returns null if no iv data', () => { + expect(getIVValueRange('primary')({ + hydrographData: {} + })).toBeNull(); + }); + + it('Returns value range of the iv data selected', () => { + const testState = { + 'hydrographData': { + primaryIVData: TEST_PRIMARY_IV_DATA, + compareIVData: TEST_COMPARE_IV_DATA + } + }; + + expect(getIVValueRange('primary')(testState)).toEqual([18.2, 20.2]); + expect(getIVValueRange('compare')(testState)).toEqual([12.9, 15.2]); + }); + }); + + describe('getGroundwaterLevelsValueRange', () => { + it('Returns null if no groundwater levels are defined', () => { + expect(getGroundwaterLevelsValueRange({ + hydrographData: {} + })).toBeNull(); + }); + + it('Returns value range of the groundwater data', () => { + expect(getGroundwaterLevelsValueRange({ + hydrographData: { + groundwaterLevels: TEST_GROUNDWATER_LEVELS + } + })).toEqual([26.2, 27.33]); + }); + }); + + describe('getPrimaryParameter', () => { + it('Returns null if no IV or groundwater levels are defined', () => { + expect(getPrimaryParameter({ + hydrographData: {} + })).toBeNull(); + }); + + it('Returns the parameter from IV if defined', () => { + expect(getPrimaryParameter({ + hydrographData: { + primaryIVData: TEST_PRIMARY_IV_DATA, + groundwaterLevels: TEST_GROUNDWATER_LEVELS + } + })).toEqual(TEST_PRIMARY_IV_DATA.parameter); + }); + + it('Return the parameter from groundwater levels if no IV data', () => { + expect(getPrimaryParameter({ + hydrographData: { + groundwaterLevels: TEST_GROUNDWATER_LEVELS + } + })).toEqual(TEST_GROUNDWATER_LEVELS.parameter); + }); + }); + + describe('getPrimaryMethods', () => { + it('Returns empty array if no IV data', () => { + expect(getPrimaryMethods({ + hydrographData: {} + })).toEqual([]); + }); + + it('Returns the methods array if primary IV Data', () => { + expect(getPrimaryMethods({ + hydrographData: { + primaryIVData: TEST_PRIMARY_IV_DATA + } + })).toEqual([{methodID: '15776'}]); + }); + }); + + describe('getPrimaryMedianStatisticsData', () => { + it('Returns null if no median statistics data', () => { + expect(getPrimaryMedianStatisticsData({ + hydrographData: {} + })).toBeNull(); + }); + + it('Returns median statistics data for current time range', () => { + expect(getPrimaryMedianStatisticsData({ + hydrographData: { + currentTimeRange: { + start: 1612280374000, + end: 1612539574000 + }, + medianStatisticsData: TEST_MEDIAN_DATA + } + })).toEqual({ + '153885': { + values: [ + {point: 16.2, dateTime: 1612280374000}, + {point: 15.9, dateTime: 1612332000000}, + {point: 16.3, dateTime: 1612418400000}, + {point: 16.3, dateTime: 1612539574000} + ], + description: 'Method1', + beginYear: '2011', + endYear: '2020' + } + }); + }); + }); + + describe('getPrimaryMedianStatisticsValueRange', () => { + it('Return null if no statistics data', () => { + expect(getPrimaryMedianStatisticsValueRange({ + hydrographData: {} + })).toBeNull(); + }); + + it('Returns median statistics value range for current time range', () => { + expect(getPrimaryMedianStatisticsValueRange({ + hydrographData: { + currentTimeRange: { + start: 1612280374000, + end: 1612539574000 + }, + medianStatisticsData: TEST_MEDIAN_DATA + } + })).toEqual([15.9, 16.3]); + }); + }); +}); \ No newline at end of file diff --git a/assets/src/scripts/monitoring-location/selectors/hydrograph-state-selector.js b/assets/src/scripts/monitoring-location/selectors/hydrograph-state-selector.js index 272697c90be151113df2989158324d514aa4b184..bb3cdb1802bb079432743cb2dcb13a0325845321 100644 --- a/assets/src/scripts/monitoring-location/selectors/hydrograph-state-selector.js +++ b/assets/src/scripts/monitoring-location/selectors/hydrograph-state-selector.js @@ -4,9 +4,11 @@ import {createSelector} from 'reselect'; import config from 'ui/config'; +/* + * The following selector functions return a function which returns the selected data. + */ export const isCompareIVDataVisible = state => state.hydrographState.showCompareIVData || false; export const isMedianDataVisible = state => state.hydrographState.showMedianData || false; - export const getSelectedDateRange = state => state.hydrographState.selectedDateRange || null; export const getSelectedCustomDateRange = state => state.hydrographState.selectedCustomDateRange || null; export const getSelectedParameterCode = state => state.hydrographState.selectedParameterCode || null; @@ -14,6 +16,11 @@ export const getSelectedIVMethodID = state => state.hydrographState.selectedIVMe export const getGraphCursorOffset = state => state.hydrographState.graphCursorOffset || null; export const getGraphBrushOffset = state => state.hydrographState.graphBrushOffset || null; +/* + * Returns a selector function that returns an Object that can be used when calling + * hydrographDataReducer action that retrieves data for the hydrograph + * @return {Function} + */ export const getInputsForRetrieval = createSelector( getSelectedParameterCode, getSelectedDateRange, diff --git a/assets/src/scripts/monitoring-location/selectors/hydrograph-state-selector.test.js b/assets/src/scripts/monitoring-location/selectors/hydrograph-state-selector.test.js new file mode 100644 index 0000000000000000000000000000000000000000..fc00bf67d3232391af771b24ce102881a3a543e8 --- /dev/null +++ b/assets/src/scripts/monitoring-location/selectors/hydrograph-state-selector.test.js @@ -0,0 +1,192 @@ +import config from 'ui/config'; + +import {isCompareIVDataVisible, isMedianDataVisible, getSelectedDateRange, getSelectedCustomDateRange, + getSelectedParameterCode, getSelectedIVMethodID, getGraphCursorOffset, getGraphBrushOffset, + getInputsForRetrieval +} from './hydrograph-state-selector'; + +describe('monitoring-location/selectors/hydrograph-state-selector', () => { + config.locationTimeZone = 'America/Chicago'; + describe('isCompareIVDataVisible', () => { + it('returns false if no compare iv data visible', () => { + expect(isCompareIVDataVisible({ + hydrographState: {} + })).toBe(false); + }); + + it('Returns true if set in the store', () => { + expect(isCompareIVDataVisible({ + hydrographState: { + showCompareIVData: true + } + })).toBe(true); + }); + }); + + describe('isMedianDataVisible', () => { + it('returns false if no median data visible', () => { + expect(isMedianDataVisible({ + hydrographState: {} + })).toBe(false); + }); + + it('Returns true if set in the store', () => { + expect(isMedianDataVisible({ + hydrographState: { + showMedianData: true + } + })).toBe(true); + }); + }); + + describe('getSelectedDateRange', () => { + it('Returns null if no selected date range', () => { + expect(getSelectedDateRange({ + hydrographState: {} + })).toBeNull(); + }); + + it('Returns selected date range', () => { + expect(getSelectedDateRange({ + hydrographState: { + selectedDateRange: 'P45D' + } + })).toEqual('P45D'); + }); + }); + + describe('getSelectedCustomDateRange', () => { + it('Returns null if no selected custom date range', () => { + expect(getSelectedCustomDateRange({ + hydrographState: {} + })).toBeNull(); + }); + + it('Returns selected custom date range', () => { + expect(getSelectedCustomDateRange({ + hydrographState: { + selectedCustomDateRange: { + start: '2021-02-01', + end: '2021-02-06' + } + } + })).toEqual({ + start: '2021-02-01', + end: '2021-02-06' + }); + }); + }); + + describe('getSelectedIVMethodID', () => { + it('Returns null if no selected method id', () => { + expect(getSelectedIVMethodID({ + hydrographState: {} + })).toBeNull(); + }); + + it('Returns selected IV method id', () => { + expect(getSelectedIVMethodID({ + hydrographState: { + selectedIVMethodID: '90649' + } + })).toEqual('90649'); + }); + }); + + describe('getGraphCursorOffset', () => { + it('Returns null if no graph cursor offset', () => { + expect(getGraphCursorOffset({ + hydrographState: {} + })).toBeNull(); + }); + + it('Returns graph cursor offset', () => { + expect(getGraphCursorOffset({ + hydrographState: { + graphCursorOffset: 362866473 + } + })).toEqual(362866473); + }); + }); + + describe('getGraphBushOffset', () => { + it('Returns null if no graph brush offset', () => { + expect(getGraphBrushOffset({ + hydrographState: {} + })).toBeNull(); + }); + + it('Returns graph brush offset', () => { + expect(getGraphBrushOffset({ + hydrographState: { + graphBrushOffset: { + start: 0, + end: 60945089 + } + } + })).toEqual({ + start: 0, + end: 60945089 + }); + }); + }); + + describe('getSelectedParameterCode', () => { + it('Returns null if no selected parameter code', () => { + expect(getSelectedParameterCode({ + hydrographState: {} + })).toBeNull(); + }); + + it('Returns selected parameter code', () => { + expect(getSelectedParameterCode({ + hydrographState: { + selectedParameterCode: '00060' + } + })).toEqual('00060'); + }); + }); + + + describe('getInputsForRetrieval', () => { + it('Return expected inputs when selectedDateRange is not custom', () => { + expect(getInputsForRetrieval({ + hydrographState: { + showCompareIVData: false, + showMedianData: true, + selectedDateRange: 'P30D', + selectedCustomDateRange: null, + selectedParameterCode: '00060' + } + })).toEqual({ + parameterCode: '00060', + period: 'P30D', + startTime: null, + endTime: null, + loadCompare: false, + loadMedian: true + }); + }); + it('Return expects inputs when selectedDateRange is custom', () => { + expect(getInputsForRetrieval({ + hydrographState: { + showCompareIVData: true, + showMedianData: false, + selectedDateRange: 'custom', + selectedCustomDateRange: { + start: '2021-02-01', + end: '2021-02-06' + }, + selectedParameterCode: '00060' + } + })).toEqual({ + parameterCode: '00060', + period: null, + startTime: '2021-02-01T00:00:00.000-06:00', + endTime: '2021-02-06T23:59:59.999-06:00', + loadCompare: true, + loadMedian: false + }); + }); + }); +}); \ No newline at end of file diff --git a/assets/src/scripts/monitoring-location/store/discrete-data.js b/assets/src/scripts/monitoring-location/store/discrete-data.js index fddd36cd136f85c797bc8a5f6670d39dc3eda025..0e5ad2d88f84e9b94142270c33bde53c90995c85 100644 --- a/assets/src/scripts/monitoring-location/store/discrete-data.js +++ b/assets/src/scripts/monitoring-location/store/discrete-data.js @@ -2,7 +2,6 @@ import {DateTime} from 'luxon'; import {fetchGroundwaterLevels} from 'ui/web-services/groundwater-levels'; -import {Actions as IVTimeSeriesDataActions} from './instantaneous-value-time-series-data'; const INITIAL_DATA = { groundwaterLevels : null }; @@ -71,7 +70,6 @@ export const retrieveGroundwaterLevels = function(monitoringLocationId, paramete variable: variable, values: values })); - dispatch(IVTimeSeriesDataActions.addVariableToIVVariables(variable)); } }, () => { diff --git a/assets/src/scripts/monitoring-location/store/discrete-data.test.js b/assets/src/scripts/monitoring-location/store/discrete-data.test.js index d2cc16a47538f76dc79761e11c68086efbf6cc2e..516cb141b4bf5d946eac2fa068fb0977752f7ca3 100644 --- a/assets/src/scripts/monitoring-location/store/discrete-data.test.js +++ b/assets/src/scripts/monitoring-location/store/discrete-data.test.js @@ -122,7 +122,7 @@ describe('monitoring-location/store/discrete-data', () => { expect(state.discreteData.groundwaterLevels['52331280'].values).toHaveLength(7); expect(state.discreteData.groundwaterLevels['52331280'].values[0]).toEqual({ value: '26.07', - qualifiers: [], + qualifiers: ['A', '1'], dateTime: 1579770360000 }); }); diff --git a/assets/src/scripts/monitoring-location/store/hydrograph-data.js b/assets/src/scripts/monitoring-location/store/hydrograph-data.js index 1341bf2f62346516dd53c4d82b2d3d971684fe8d..1e4040ef00d202b310382b792a7b04cbbf07a2f5 100644 --- a/assets/src/scripts/monitoring-location/store/hydrograph-data.js +++ b/assets/src/scripts/monitoring-location/store/hydrograph-data.js @@ -103,47 +103,49 @@ const retrieveIVData = function(siteno, dataKind, {parameterCode, period, startT startTime: startTime, endTime: endTime }).then(data => { - const tsData = data.value.timeSeries[0]; - // Create parameter object and adjust data if parameter code is calculated - let parameter = { - parameterCode: tsData.variable.variableCode[0].value, - name: tsData.variable.variableName, - description: tsData.variable.variableDescription, - unit: tsData.variable.unit.unitCode - }; - if (isCalculatedTemperatureCode) { - parameter = getConvertedTemperatureParameter(parameter); - } + if (data.value && data.value.timeSeries && data.value.timeSeries.length) { + const tsData = data.value.timeSeries[0]; + // Create parameter object and adjust data if parameter code is calculated + let parameter = { + parameterCode: tsData.variable.variableCode[0].value, + name: tsData.variable.variableName, + description: tsData.variable.variableDescription, + unit: tsData.variable.unit.unitCode + }; + if (isCalculatedTemperatureCode) { + parameter = getConvertedTemperatureParameter(parameter); + } - // Convert values from strings to float and set to null if they have the noDataValue. - // If calculated parameter code, update the value. - const noDataValue = tsData.variable.noDataValue; - const values = tsData.values.reduce((valuesByMethodId, value) => { - valuesByMethodId[value.method[0].methodID] = { - points: value.value.map(point => { - let pointValue = parseFloat(point.value); - pointValue = pointValue === noDataValue ? null : pointValue; - if (pointValue && isCalculatedTemperatureCode) { - pointValue = parseFloat(convertCelsiusToFahrenheit(pointValue).toFixed(2)); + // Convert values from strings to float and set to null if they have the noDataValue. + // If calculated parameter code, update the value. + const noDataValue = tsData.variable.noDataValue; + const values = tsData.values.reduce((valuesByMethodId, value) => { + valuesByMethodId[value.method[0].methodID] = { + points: value.value.map(point => { + let pointValue = parseFloat(point.value); + pointValue = pointValue === noDataValue ? null : pointValue; + if (pointValue && isCalculatedTemperatureCode) { + pointValue = parseFloat(convertCelsiusToFahrenheit(pointValue).toFixed(2)); + } + return { + value: pointValue, + qualifiers: point.qualifiers, + dateTime: DateTime.fromISO(point.dateTime).toMillis() + }; + }), + method: { + ...value.method[0], + methodID: value.method[0].methodID.toString() } - return { - value: pointValue, - qualifiers: point.qualifiers, - dateTime: DateTime.fromISO(point.dateTime).toMillis() - }; - }), - method: { - ...value.method[0], - methodID: value.method[0].methodID.toString() - } - }; - return valuesByMethodId; - }, {}); + }; + return valuesByMethodId; + }, {}); - dispatch(addIVHydrographData(dataKind, { - parameter, - values - })); + dispatch(addIVHydrographData(dataKind, { + parameter, + values + })); + } }); }; }; diff --git a/assets/src/scripts/monitoring-location/store/hydrograph-data.test.js b/assets/src/scripts/monitoring-location/store/hydrograph-data.test.js index ff94ed917a95871d0180bfaa24e04dd82c7c574f..7f58cf4c61a47a6a5c20df4a0572a28a32b7e1f4 100644 --- a/assets/src/scripts/monitoring-location/store/hydrograph-data.test.js +++ b/assets/src/scripts/monitoring-location/store/hydrograph-data.test.js @@ -4,7 +4,7 @@ import {applyMiddleware, combineReducers, createStore} from 'redux'; import {default as thunk} from 'redux-thunk'; import sinon from 'sinon'; -import {MOCK_IV_DATA, MOCK_GWLEVEL_DATA, MOCK_STATISTICS_JSON} from 'ui/mock-service-data'; +import {MOCK_IV_DATA, MOCK_TEMP_C_IV_DATA, MOCK_GWLEVEL_DATA, MOCK_STATISTICS_JSON} from 'ui/mock-service-data'; import * as ivDataService from 'ui/web-services/instantaneous-values'; import * as groundwaterLevelService from 'ui/web-services/groundwater-levels'; @@ -12,7 +12,7 @@ import * as statisticsDataService from 'ui/web-services/statistics-data'; import config from 'ui/config'; -import {hydrographDataReducer, retrieveHydrographData, retrieveMedianStatistics, retrievePriorYearIVData } from './hydrograph-data'; +import {hydrographDataReducer, retrieveHydrographData, retrieveMedianStatistics, retrievePriorYearIVData} from './hydrograph-data'; describe('monitoring-location/store/hydrograph-data', () => { let store; @@ -21,13 +21,6 @@ describe('monitoring-location/store/hydrograph-data', () => { config.locationTimeZone = 'America/Chicago'; - ivDataService.fetchTimeSeries = - jest.fn().mockReturnValue(Promise.resolve(JSON.parse(MOCK_IV_DATA))); - groundwaterLevelService.fetchGroundwaterLevels = - jest.fn().mockReturnValue(Promise.resolve(JSON.parse(MOCK_GWLEVEL_DATA))); - statisticsDataService.fetchSiteStatistics = - jest.fn().mockReturnValue(Promise.resolve(MOCK_STATISTICS_JSON)); - luxon.DateTime.local = jest.fn().mockReturnValue(luxon.DateTime.fromISO('2021-02-10T12:00')); beforeEach(() => { store = createStore( @@ -50,6 +43,15 @@ describe('monitoring-location/store/hydrograph-data', () => { describe('retrieveHydrographData', () => { describe('The correct web services are called', () => { + beforeEach(() => { + ivDataService.fetchTimeSeries = + jest.fn().mockReturnValue(Promise.resolve(JSON.parse(MOCK_IV_DATA))); + groundwaterLevelService.fetchGroundwaterLevels = + jest.fn().mockReturnValue(Promise.resolve(JSON.parse(MOCK_GWLEVEL_DATA))); + statisticsDataService.fetchSiteStatistics = + jest.fn().mockReturnValue(Promise.resolve(MOCK_STATISTICS_JSON)); + }); + it('Expects to retrieve groundwater and IV data if both are in the period of record', () => { config.ivPeriodOfRecord = { '00060': {begin_date: '2010-01-01', end_date: '2020-01-01'} @@ -206,10 +208,219 @@ describe('monitoring-location/store/hydrograph-data', () => { expect(mockStatsCalls).toHaveLength(0); }); }); - /* - TODO: Tests to consider adding: Tests to make sure data is inserted into the store correctly, - Tests to see if temperature calculation for fahrenheit works. - */ + + describe('data is loaded into the Redux store', () => { + beforeEach(() => { + ivDataService.fetchTimeSeries = + jest.fn().mockReturnValue(Promise.resolve(JSON.parse(MOCK_IV_DATA))); + groundwaterLevelService.fetchGroundwaterLevels = + jest.fn().mockReturnValue(Promise.resolve(JSON.parse(MOCK_GWLEVEL_DATA))); + statisticsDataService.fetchSiteStatistics = + jest.fn().mockReturnValue(Promise.resolve(MOCK_STATISTICS_JSON)); + }); + + it('Expect IV data is stored when available', () => { + config.ivPeriodOfRecord = { + '00060': {begin_date: '2010-01-01', end_date: '2020-01-01'} + }; + config.gwPeriodOfRecord = {}; + + return store.dispatch(retrieveHydrographData('11112222', { + parameterCode: '00060', + period: 'P7D', + startTime: null, + endTime: null, + loadCompare: false, + loadMedian: true + })).then(() => { + const hydrographData = store.getState().hydrographData; + + expect(hydrographData.currentTimeRange).toEqual({ + start: 1612375200000, + end: 1612980000000 + }); + expect(hydrographData.primaryIVData.parameter).toEqual({ + parameterCode: '00060', + name: 'Streamflow, ft³/s', + description: 'Discharge, cubic feet per second', + unit: 'ft3/s' + }); + expect(hydrographData.primaryIVData.values['158049'].points).toHaveLength(670); + expect(hydrographData.primaryIVData.values['158049'].points[0]).toEqual({ + value: 302, + qualifiers: ['P'], + dateTime: 1514926800000 + }); + }); + }); + + it('Expect GW data is stored when requested', () => { + config.ivPeriodOfRecord = {}; + config.gwPeriodOfRecord = { + '72019': {begin_date: '2010-01-01', end_date: '2020-01-01'} + }; + + return store.dispatch(retrieveHydrographData('11112222', { + parameterCode: '72019', + period: 'P7D', + startTime: null, + endTime: null, + loadCompare: false, + loadMedian: true + })).then(() => { + const hydrographData = store.getState().hydrographData; + + expect(hydrographData.currentTimeRange).toEqual({ + start: 1612375200000, + end: 1612980000000 + }); + + expect(hydrographData.groundwaterLevels.parameter).toEqual({ + parameterCode: '72019', + name: 'Depth to water level, ft below land surface', + description: 'Depth to water level, feet below land surface', + unit: 'ft' + }); + expect(hydrographData.groundwaterLevels.values).toHaveLength(7); + expect(hydrographData.groundwaterLevels.values[0]).toEqual({ + value: 26.07, + qualifiers: ['A', '1'], + dateTime: 1579770360000 + }); + }); + }); + }); + + describe('retrieveHydrographData when calculated Fahrenheit is requested', () => { + beforeEach(() => { + ivDataService.fetchTimeSeries = + jest.fn().mockReturnValue(Promise.resolve(JSON.parse(MOCK_TEMP_C_IV_DATA))); + config.ivPeriodOfRecord = { + '00010': {begin_date: '2010-01-01', end_date: '2020-01-01'} + }; + config.gwPeriodOfRecord = {}; + }); + + it('Expects celsius temperature to be retrieved when calculated is requested', () => { + store.dispatch(retrieveHydrographData('11112222', { + parameterCode: '00010F', + period: 'P7D', + startTime: null, + endTime: null, + loadCompare: false, + loadMedian: true + })); + + const mockIVCalls = ivDataService.fetchTimeSeries.mock.calls; + expect(mockIVCalls).toHaveLength(1); + expect(mockIVCalls[0][0]).toEqual({ + sites: ['11112222'], + parameterCode: '00010', + period: 'P7D', + startTime: null, + endTime: null + }); + }); + + it('Expects calculated fahrenheit to be stored when requested', () => { + return store.dispatch(retrieveHydrographData('11112222', { + parameterCode: '00010F', + period: 'P7D', + startTime: null, + endTime: null, + loadCompare: false, + loadMedian: true + })).then(() => { + const hydrographData = store.getState().hydrographData; + + expect(hydrographData.primaryIVData.parameter).toEqual({ + parameterCode : '00010F', + name: 'Temperature, water, °F (calculated)', + description: 'Temperature, water, degrees Fahrenheit (calculated)', + unit: 'deg F' + }); + }); + + }); + }); + }); + + describe('retrievePriorYearIVData', () => { + beforeEach(() => { + ivDataService.fetchTimeSeries = + jest.fn().mockReturnValue(Promise.resolve(JSON.parse(MOCK_IV_DATA))); + config.ivPeriodOfRecord = { + '00060': {begin_date: '2010-01-01', end_date: '2020-01-01'} + }; + + return store.dispatch(retrieveHydrographData('11112222', { + parameterCode: '00060', + period: 'P7D', + startTime: null, + endTime: null, + loadCompare: false, + loadMedian: false + })); + }); + + it('Expects a call to retrievePriorYearIVData sets the prioryearTimeRange and fetches the data', () => { + const currentTimeRange = store.getState().hydrographData.currentTimeRange; + return store.dispatch(retrievePriorYearIVData('11112222', { + parameterCode: '00060', + startTime: currentTimeRange.start, + endTime: currentTimeRange.end + })).then(() => { + const hydrographData = store.getState().hydrographData; + expect(hydrographData.prioryearTimeRange).toEqual({ + start: 1580839200000, + end: 1581444000000 + }); + const mockIVCalls = ivDataService.fetchTimeSeries.mock.calls; + expect(mockIVCalls).toHaveLength(2); + expect(mockIVCalls[1][0]).toEqual({ + sites: ['11112222'], + parameterCode: '00060', + period: undefined, + startTime: '2020-02-04T12:00:00.000-06:00', + endTime: '2020-02-11T12:00:00.000-06:00' + }); + + expect(hydrographData.compareIVData).toBeDefined(); + }); + }); }); -}); + describe('retrieveMedianStatistics', () => { + beforeEach(() => { + ivDataService.fetchTimeSeries = + jest.fn().mockReturnValue(Promise.resolve(JSON.parse(MOCK_IV_DATA))); + statisticsDataService.fetchSiteStatistics = + jest.fn().mockReturnValue(Promise.resolve(MOCK_STATISTICS_JSON)); + config.ivPeriodOfRecord = { + '00060': {begin_date: '2010-01-01', end_date: '2020-01-01'} + }; + + return store.dispatch(retrieveHydrographData('11112222', { + parameterCode: '00060', + period: 'P7D', + startTime: null, + endTime: null, + loadCompare: false, + loadMedian: false + })); + }); + + it('Expects median data to fetched and stored', () => { + return store.dispatch(retrieveMedianStatistics('11112222', '00060')).then(() => { + const mockStatsCalls = statisticsDataService.fetchSiteStatistics.mock.calls; + expect(mockStatsCalls).toHaveLength(1); + expect(mockStatsCalls[0][0]).toEqual({ + siteno: '11112222', + statType: 'median', + params: ['00060'] + }); + expect(store.getState().hydrographData.medianStatisticsData).toBeDefined(); + }); + }); + }); +}); \ No newline at end of file diff --git a/assets/src/scripts/monitoring-location/store/hydrograph-parameters.js b/assets/src/scripts/monitoring-location/store/hydrograph-parameters.js index 1b990d69d0cb5ab4c98c60235d2c17e1dfc88a6a..50ad00b1e4d25c383b3595150e9207114de83540 100644 --- a/assets/src/scripts/monitoring-location/store/hydrograph-parameters.js +++ b/assets/src/scripts/monitoring-location/store/hydrograph-parameters.js @@ -6,7 +6,7 @@ import config from 'ui/config'; import {fetchGroundwaterLevels} from 'ui/web-services/groundwater-levels'; import {fetchTimeSeries} from 'ui/web-services/instantaneous-values'; -import {getConvertedTemperatureParameter} from 'ml/iv-data-utils'; +import {getConvertedTemperatureParameter, hasMeasuredFahrenheitParameter} from 'ml/iv-data-utils'; /* * Synchronous Redux action - updatethe hydrograph variables @@ -26,43 +26,57 @@ const updateHydrographParameters = function(parameters) { */ export const retrieveHydrographParameters = function(siteno) { return function(dispatch) { - const fetchIVParameters = fetchTimeSeries({sites: [siteno]}).then(series => { - return series.value.timeSeries.reduce((varsByPCode, ts) => { - const parameterCode = ts.variable.variableCode[0].value; - varsByPCode[parameterCode] = { - parameterCode: parameterCode, - name: ts.variable.variableName, - description: ts.variable.variableDescription, - unit: ts.variable.unit.unitCode, - hasIVData: true, - ivMethods: ts.values.map(value => { - return { - description: value.method.methodDescription, - methodID: value.method.methodID + const fetchIVParameters = fetchTimeSeries({sites: [siteno]}) + .then(series => { + if (series.value && series.value.timeSeries) { + const allParameterCodes = series.value.timeSeries.map(ts => ts.variable.variableCode[0].value); + return series.value.timeSeries.reduce((varsByPCode, ts) => { + const parameterCode = ts.variable.variableCode[0].value; + varsByPCode[parameterCode] = { + parameterCode: parameterCode, + name: ts.variable.variableName, + description: ts.variable.variableDescription, + unit: ts.variable.unit.unitCode, + hasIVData: true }; - }) - }; - // If it is a celsius parameterCode, add a variable for calculated Fahrenheit. - if (config.TEMPERATURE_PARAMETERS.celsius.includes(parameterCode)) { - const fahrenheitParameter = getConvertedTemperatureParameter(varsByPCode[parameterCode]); - varsByPCode[fahrenheitParameter.parameterCode] = fahrenheitParameter; + // If it is a celsius parameterCode, add a variable for calculated Fahrenheit. + if (config.TEMPERATURE_PARAMETERS.celsius.includes(parameterCode) && + !hasMeasuredFahrenheitParameter(parameterCode, allParameterCodes)) { + const fahrenheitParameter = getConvertedTemperatureParameter(varsByPCode[parameterCode]); + varsByPCode[fahrenheitParameter.parameterCode] = fahrenheitParameter; + } + return varsByPCode; + }, {}); + } else { + return null; } - return varsByPCode; - }, {}); - }); - const fetchGWLevelParameters = fetchGroundwaterLevels({site: siteno}).then(series => { - return series.value.timeSeries.reduce((varsByPCode, ts) => { - const parameterCode = ts.variable.variableCode[0].value; - varsByPCode[parameterCode] = { - parameterCode: parameterCode, - name: ts.variable.variableName, - description: ts.variable.variableDescription, - unit: ts.variable.unit.unitCode, - hasGWLevelsData: true - }; - return varsByPCode; - }, {}); - }); + }) + .catch(reason => { + console.error(reason); + throw reason; + }); + const fetchGWLevelParameters = fetchGroundwaterLevels({site: siteno}) + .then(series => { + if (series.value && series.value.timeSeries) { + return series.value.timeSeries.reduce((varsByPCode, ts) => { + const parameterCode = ts.variable.variableCode[0].value; + varsByPCode[parameterCode] = { + parameterCode: parameterCode, + name: ts.variable.variableName, + description: ts.variable.variableDescription, + unit: ts.variable.unit.unitCode, + hasGWLevelsData: true + }; + return varsByPCode; + }, {}); + } else { + return null; + } + }) + .catch(reason => { + console.error(reason); + throw reason; + }); return Promise.all([fetchIVParameters, fetchGWLevelParameters]).then(([ivVars, gwVars]) => { const mergedVars = merge({}, gwVars, ivVars); dispatch(updateHydrographParameters(mergedVars)); diff --git a/assets/src/scripts/monitoring-location/store/hydrograph-parameters.test.js b/assets/src/scripts/monitoring-location/store/hydrograph-parameters.test.js new file mode 100644 index 0000000000000000000000000000000000000000..c0abb83d705036acdd39023fd1ee365923296825 --- /dev/null +++ b/assets/src/scripts/monitoring-location/store/hydrograph-parameters.test.js @@ -0,0 +1,87 @@ +import mockConsole from 'jest-mock-console'; +import {applyMiddleware, combineReducers, createStore} from 'redux'; +import {default as thunk} from 'redux-thunk'; +import sinon from 'sinon'; + +import {MOCK_LATEST_IV_DATA, MOCK_LATEST_GW_DATA} from 'ui/mock-service-data'; + +import * as ivDataService from 'ui/web-services/instantaneous-values'; +import * as groundwaterLevelService from 'ui/web-services/groundwater-levels'; + +import {hydrographParametersReducer, retrieveHydrographParameters} from './hydrograph-parameters'; +import {hydrographDataReducer} from "./hydrograph-data"; + +describe('monitoring-location/store/hydrograph-parameters', () => { + let store; + let fakeServer; + let restoreConsole; + + beforeEach(() => { + store = createStore( + combineReducers({ + hydrographParameters: hydrographParametersReducer + }), + { + hydrographParameters: {} + }, + applyMiddleware(thunk) + ); + fakeServer = sinon.createFakeServer(); + restoreConsole = mockConsole(); + }); + + afterEach(() => { + fakeServer.restore(); + restoreConsole(); + }); + + describe('retrieveHydrographParameters', () => { + + beforeEach(() => { + ivDataService.fetchTimeSeries = + jest.fn().mockReturnValue(Promise.resolve(JSON.parse(MOCK_LATEST_IV_DATA))); + groundwaterLevelService.fetchGroundwaterLevels = + jest.fn().mockReturnValue(Promise.resolve(JSON.parse(MOCK_LATEST_GW_DATA))); + }); + + it('Expects the latest IV and GW data to be fetched', () => { + store.dispatch(retrieveHydrographParameters('11112222')); + + const mockIVCalls = ivDataService.fetchTimeSeries.mock.calls; + const mockGWCalls = groundwaterLevelService.fetchGroundwaterLevels.mock.calls; + + expect(mockIVCalls).toHaveLength(1); + expect(mockIVCalls[0][0]).toEqual({ + sites: ['11112222'] + }); + expect(mockGWCalls).toHaveLength(1); + expect(mockGWCalls[0][0]).toEqual({ + site: '11112222' + }); + }); + + it('Expects the Redux store to save the parameters from both IV and GW calls', () => { + return store.dispatch(retrieveHydrographParameters('11112222')).then(() => { + const hydrographParameters = store.getState().hydrographParameters; + + expect(hydrographParameters['00010']).toBeDefined(); + expect(hydrographParameters['00010F']).toBeDefined(); + expect(hydrographParameters['72019']).toBeDefined(); + expect(hydrographParameters['62610']).toBeDefined(); + expect(hydrographParameters['62611']).toBeDefined(); + expect(hydrographParameters['00010'].hasIVData).toBeTruthy(); + expect(hydrographParameters['00010F'].hasIVData).toBeTruthy(); + expect(hydrographParameters['72019'].hasIVData).toBeTruthy(); + expect(hydrographParameters['62610'].hasIVData).toBeFalsy(); + expect(hydrographParameters['62611'].hasIVData).toBeFalsy(); + expect(hydrographParameters['00010'].hasGWLevelsData).toBeFalsy(); + expect(hydrographParameters['00010F'].hasGWLevelsData).toBeFalsy(); + expect(hydrographParameters['72019'].hasGWLevelsData).toBeTruthy(); + expect(hydrographParameters['62610'].hasGWLevelsData).toBeTruthy(); + expect(hydrographParameters['62611'].hasGWLevelsData).toBeTruthy(); + + }); + }); + }); +}); + diff --git a/assets/src/scripts/monitoring-location/store/hydrograph-state.js b/assets/src/scripts/monitoring-location/store/hydrograph-state.js index 5b0e1cf73f3235ae22fda3e13d7dfe1d85a792ed..2be71e2fd50770f19da0463680a58dbba438f054 100644 --- a/assets/src/scripts/monitoring-location/store/hydrograph-state.js +++ b/assets/src/scripts/monitoring-location/store/hydrograph-state.js @@ -7,11 +7,6 @@ export const INITIAL_STATE = { selectedIVMethodID: null, graphCursorOffset: null, graphBrushOffset: null, - userInputsForTimeRange: { - mainTimeRangeSelectionButton: 'P7D', - customTimeRangeSelectionButton: 'days-input', - numberOfDaysFieldValue: '' - } }; /* diff --git a/assets/src/scripts/monitoring-location/store/network.test.js b/assets/src/scripts/monitoring-location/store/network.test.js index 3fa2bb841109503166fb6f7ed458169e8253401e..a57104c1f1dd5a53f0b74659256e1518722bdbc8 100644 --- a/assets/src/scripts/monitoring-location/store/network.test.js +++ b/assets/src/scripts/monitoring-location/store/network.test.js @@ -1,3 +1,4 @@ +import mockConsole from 'jest-mock-console'; import {applyMiddleware, combineReducers, createStore} from 'redux'; import {default as thunk} from 'redux-thunk'; import sinon from 'sinon'; @@ -10,6 +11,7 @@ describe('monitoring-location/store/network module', () => { /* eslint no-use-before-define: 0 */ let store; let fakeServer; + let restoreConsole; beforeEach(() => { store = createStore( @@ -22,10 +24,12 @@ describe('monitoring-location/store/network module', () => { applyMiddleware(thunk) ); fakeServer = sinon.createFakeServer(); + restoreConsole = mockConsole(); }); afterEach(() => { fakeServer.restore(); + restoreConsole(); }); describe('networkDataReducer and Actions', () => { diff --git a/assets/src/scripts/monitoring-location/url-params.test.js b/assets/src/scripts/monitoring-location/url-params.test.js index c9ffd2a8fb24ae4d6fad8303b2c20d14cecb6aa5..b75a08af2c5191dbe45297205e1c00745a3126a9 100644 --- a/assets/src/scripts/monitoring-location/url-params.test.js +++ b/assets/src/scripts/monitoring-location/url-params.test.js @@ -1,5 +1,5 @@ import {configureStore} from 'ml/store'; -import {Actions} from 'ml/store/instantaneous-value-time-series-state'; +import {setSelectedDateRange} from './store/hydrograph-state'; import {getParamString, renderTimeSeriesUrlParams} from 'ml/url-params'; describe('monitoring-location/url-params module', () => { @@ -20,55 +20,28 @@ describe('monitoring-location/url-params module', () => { describe('renderTimeSeriesUrlParams', () => { const TEST_STATE = { - ivTimeSeriesData: { - variables: { - '123456': { - oid: '123456', - variableCode: { - value: '00010' + hydrographData: { + primaryIVData: { + values: { + '69928': { + method: { + methodID: '69928' + } } - }, - '123457': { - oid: '123457', - variableCode: { - value: '00065' - } - } - }, - methods: { - '69928': { - methodDescription: 'Method 69928', - methodID: 69928 - }, - '69929': { - methodDescription: 'Method 69929', - methodID: 69929 - } - }, - timeSeries: { - '69928:current:P7D': { - tsKey: 'current:P7D', - method: 69928, - variable: '123456' } } }, - ianaTimeZone: 'America/New_York', - ivTimeSeriesState: { - currentIVVariableID: '123456', - currentIVDateRange: 'P7D', - customIVTimeRange: null, - currentIVMethodID: '69928', - showIVTimeSeries: { - compare: false - } + hydrographState: { + showCompareIVData: false, + selectedParameterCode: '00010', + selectedDateRange: 'P7D', + selectedCustomDateRange: null, + selectedIVMethodID: '69928' } }; it('adds nothing to the window.location.hash when the store is empty', () => { let store = configureStore({ - ivTimeSeriesState: { - showIVTimeSeries: {} - } + hydrographState: {} }); renderTimeSeriesUrlParams(store); expect(window.location.hash).toEqual(''); @@ -88,11 +61,9 @@ describe('monitoring-location/url-params module', () => { it('adds compare if compare is in the store', () => { let store = configureStore({ ...TEST_STATE, - ivTimeSeriesState: { - ...TEST_STATE.ivTimeSeriesState, - showIVTimeSeries: { - compare: true - } + hydrographState: { + ...TEST_STATE.hydrographState, + showCompareIVData: true } }); renderTimeSeriesUrlParams(store); @@ -105,12 +76,12 @@ describe('monitoring-location/url-params module', () => { expect(window.location.hash).not.toContain('timeSeriesId'); }); - it('adds period if current date range is P30D or P1Y', () => { + it('adds period if current date range is P30D or P365D', () => { let store = configureStore({ ...TEST_STATE, - ivTimeSeriesState: { - ...TEST_STATE.ivTimeSeriesState, - currentIVDateRange: 'P30D' + hydrographState: { + ...TEST_STATE.hydrographState, + selectedDateRange: 'P30D' } }); renderTimeSeriesUrlParams(store); @@ -122,36 +93,10 @@ describe('monitoring-location/url-params module', () => { expect(window.location.hash).not.toContain('endDT'); expect(window.location.hash).not.toContain('timeSeriesId'); - store.dispatch(Actions.setCurrentIVDateRange('P1Y')); - return new Promise(resolve => { - window.requestAnimationFrame(() => { - expect(window.location.hash).toContain('period=P1Y'); - resolve(); - }); - }); - }); - - it('adds period if current date range not P7D and is in the form of P{some number}{Day or Year code}', () => { - let store = configureStore({ - ...TEST_STATE, - ivTimeSeriesState: { - ...TEST_STATE.ivTimeSeriesState, - currentIVDateRange: 'P23D' - } - }); - renderTimeSeriesUrlParams(store); - - expect(window.location.hash).toContain('parameterCode=00010'); - expect(window.location.hash).not.toContain('compare=true'); - expect(window.location.hash).toContain('period=P23D'); - expect(window.location.hash).not.toContain('startDT'); - expect(window.location.hash).not.toContain('endDT'); - expect(window.location.hash).not.toContain('timeSeriesId'); - - store.dispatch(Actions.setCurrentIVDateRange('P1Y')); + store.dispatch(setSelectedDateRange('P20D')); return new Promise(resolve => { window.requestAnimationFrame(() => { - expect(window.location.hash).toContain('period=P1Y'); + expect(window.location.hash).toContain('period=P20D'); resolve(); }); }); @@ -160,9 +105,9 @@ describe('monitoring-location/url-params module', () => { it('does not add period if current date range is P7D', () => { let store = configureStore({ ...TEST_STATE, - ivTimeSeriesState: { - ...TEST_STATE.ivTimeSeriesState, - currentIVDateRange: 'P23D' + hydrographState: { + ...TEST_STATE.hydrographState, + selectedDateRange: 'P7D' } }); renderTimeSeriesUrlParams(store); @@ -174,24 +119,24 @@ describe('monitoring-location/url-params module', () => { expect(window.location.hash).not.toContain('endDT'); expect(window.location.hash).not.toContain('timeSeriesId'); - store.dispatch(Actions.setCurrentIVDateRange('P1Y')); + store.dispatch(setSelectedDateRange('P365D')); return new Promise(resolve => { window.requestAnimationFrame(() => { - expect(window.location.hash).toContain('period=P1Y'); + expect(window.location.hash).toContain('period=P365D'); resolve(); }); }); }); - it('Contains startDT and endDT in url if customTimeRange is set in store', () => { + it('Contains startDT and endDT in url if selectedCustomDateRange is set in store', () => { let store = configureStore({ ...TEST_STATE, - ivTimeSeriesState: { - ...TEST_STATE.ivTimeSeriesState, - currentIVDateRange: 'custom', - customIVTimeRange: { - start: 1546318800000, - end: 1551416400000 + hydrographState: { + ...TEST_STATE.hydrographState, + selectedDateRange: 'custom', + selectedCustomDateRange: { + 'start': '2020-03-01', + 'end': '2020-03-15' } } }); @@ -200,26 +145,25 @@ describe('monitoring-location/url-params module', () => { expect(window.location.hash).toContain('parameterCode=00010'); expect(window.location.hash).not.toContain('compare=true'); expect(window.location.hash).not.toContain('period'); - expect(window.location.hash).toContain('startDT=2019-01-01'); - expect(window.location.hash).toContain('endDT=2019-03-01'); + expect(window.location.hash).toContain('startDT=2020-03-01'); + expect(window.location.hash).toContain('endDT=2020-03-15'); expect(window.location.hash).not.toContain('timeSeriesId'); }); - it('expects timeSeriesId to be set if currentMethodId is not null and multiple time series in selected variable', () => { + it('expects timeSeriesId to be set if selectedMethodId is not null and there are multiple methods', () => { let store = configureStore({ ...TEST_STATE, - ivTimeSeriesData: { - ...TEST_STATE.ivTimeSeriesData, - timeSeries: { - '69928:current:P7D': { - tsKey: 'current:P7D', - method: 69928, - variable: '123456' - }, - '69929:current:P7D': { - tsKey: 'current:P7D', - method: 69929, - variable: '123456' + hydrographData: { + ...TEST_STATE.hydrographData, + primaryIVData: { + ...TEST_STATE.hydrographData.primaryIVData, + values: { + ...TEST_STATE.hydrographData.primaryIVData.values, + '69929': { + method: { + methodID: '69929' + } + } } } } diff --git a/assets/src/scripts/utils.js b/assets/src/scripts/utils.js index d4917636f31befb1d50576cb95c2b1e3e0c0fb33..7082d0f482e5685d34b59a444d7dd4d7ebde11c3 100644 --- a/assets/src/scripts/utils.js +++ b/assets/src/scripts/utils.js @@ -290,3 +290,20 @@ export const getNearestTime = function(data, time) { // Return the nearest data point and its index. return datum; }; + +/* +* When given an approval code, will return the text equivalent +* @param {String} approvalCode - Usually a letter such as 'A' +* @return {String} - an easy to understand text version of an approval code +*/ +export const approvalCodeText = function(approvalCode) { + const approvalText = { + P: 'Provisional', + A: 'Approved', + R: 'Revised', + E: 'Estimated', + default: `unknown code: ${approvalCode}` + }; + + return approvalText[approvalCode] || approvalText.default; +}; diff --git a/assets/src/scripts/utils.test.js b/assets/src/scripts/utils.test.js index 53d95eea9946df190d46790f7df21f22d0a4e85e..1c590ab8a13be9267065bd8f5fab1d8a75085cdf 100644 --- a/assets/src/scripts/utils.test.js +++ b/assets/src/scripts/utils.test.js @@ -1,12 +1,10 @@ -import {select} from 'd3-selection'; import { unicodeHtmlEntity, getHtmlFromString, replaceHtmlEntities, setEquality, - wrap, mediaQuery, calcStartTime, callIf, parseRDB, convertFahrenheitToCelsius, + calcStartTime, callIf, parseRDB, convertFahrenheitToCelsius, convertCelsiusToFahrenheit, sortedParameters, getNearestTime} from './utils'; describe('Utils module', () => { - describe('unicodeHtmlEntity', () => { it('Can determine the unicode of a decimal entity', () => { @@ -145,46 +143,45 @@ describe('Utils module', () => { describe('sortedParameters', () => { const testVars = { - 20214: { - oid: '20214', - variableCode: {'value': '00065', variableID: 20214}, - variableDescription: 'Gage Height, meters', - variableName: 'Gage Height, m' - }, - 1701: { - oid: '1701', - variableCode: {value: '91102', variableID: 1701}, - variableDescription: 'Mass, cobalt(II) chloride, kilogram', - variableName: 'Mass, cobalt(II) chloride, kg' + '00060': { + parameterCode: '00060', + name: 'Streamflow, ft3/s', + description: 'Discharge, cubic feet per second', + unit: 'ft3/s', + hasIVData: true }, - 501: { - oid: '501', - variableCode: {value: '20018', variableID: 501}, - variableDescription: 'Volume, bromine, liter', - variableName: 'Volume, bromine, L' + '00010': { + parameterCode: '00010', + name: 'Temperature, water, C', + description: 'Temperature, water, degrees Celsius', + unit: 'deg C', + hasIVData: true }, - 17778: { - oid: '17778', - variableCode: {value: '72019', variableID: 17778}, - variableDescription: 'Depth to water level, meters below land surface', - variableName: 'Depth to water level, m' + '00010F': { + parameterCode: '00010F', + name: 'Temperature, water, F', + description: 'Temperature, water, degrees Fahrenheit', + unit: 'deg F', + hasIVData: true }, - 42: { - oid: '42', - variableCode: {value: '83452', variableID: 42}, - variableDescription: 'Cross-sectional area, millibarn', - variableName: 'Cross-sectional area, mb' + '72019': { + parameterCode: '72019', + name: 'Depth to water level, ft below land surface', + description: 'Depth to water level, feet below land surface', + unit: 'ft', + hasIVData: true, + hasGWLevelsData: true }, - 88450: { - oid: '88450', - variableCode: {value: '00060', variableID: 88450}, - variableDescription: 'Discharge, cubic meters per second', - variableName: 'Discharge, m3/s' + '62610': { + parameterCode: '62610', + name: 'Groundwater level above NGVD 1929, feet', + description: 'Groundwater level above NGVD 1929, feet', + unit: 'ft', + hasGWLevelsData: true } }; - it('sorts a group of parameters', () => { - expect(sortedParameters(testVars).map(x => x.oid)).toEqual(['20214', '88450', '17778', '42', '1701', '501']); + expect(sortedParameters(testVars).map(x => x.parameterCode)).toEqual(['00060', '72019', '62610', '00010', '00010F']); }); it('handles the case where variables are empty', () => { diff --git a/assets/src/scripts/web-services/flood-data.js b/assets/src/scripts/web-services/flood-data.js index 06f5cf75baf7f645c97b59e4760182d7209181c6..f489016968fb2fc4889a0c906f91d934cf959357 100644 --- a/assets/src/scripts/web-services/flood-data.js +++ b/assets/src/scripts/web-services/flood-data.js @@ -68,8 +68,11 @@ export const fetchFloodExtent = function(siteno) { const fetchWaterwatchData = function(waterwatchQuery, siteno) { return get(waterwatchQuery) .then((responseText) => { - const responseJson = JSON.parse(responseText).sites[0]; - return responseJson ? responseJson : null; + const response = JSON.parse(responseText); + if (!response.sites || !response.sites.length) { + return null; + } + return response.sites[0]; }) .catch(reason => { console.log(`Unable to get Waterwatch data for ${siteno} with reason: ${reason}`); diff --git a/assets/src/scripts/web-services/groundwater-levels.test.js b/assets/src/scripts/web-services/groundwater-levels.test.js index 6ea5e50faebbbd39580fa8e969c2098ca09ec0fc..542833fbfb4f963719214d400afbd45bbf503241 100644 --- a/assets/src/scripts/web-services/groundwater-levels.test.js +++ b/assets/src/scripts/web-services/groundwater-levels.test.js @@ -26,7 +26,7 @@ describe('web-services/groundwater-levels', () => { site: '354133082042203', parameterCode: '72019', startDT: '2020-01-01', - endDt: '2020-11-17' + endDT: '2020-11-17' }; it('expects properly formated query parameters in service request', () => { @@ -35,11 +35,37 @@ describe('web-services/groundwater-levels', () => { expect(url).toContain(`sites=${fetchParameters.site}`); expect(url).toContain(`parameterCd=${fetchParameters.parameterCode}`); + expect(url).not.toContain('period'); expect(url).toContain(`startDT=${fetchParameters.startDT}`); expect(url).toContain(`endDT=${fetchParameters.endDT}`); expect(url).toContain('format=json'); }); + it('expects period to be in query parameters if no startDT and endDT', () => { + fetchGroundwaterLevels({ + site: '354133082042203', + parameterCode: '72019', + period: 'P7D' + }); + const url = fakeServer.requests[0].url; + + expect(url).toContain('period=P7D'); + expect(url).not.toContain('startDT'); + expect(url).not.toContain('endDT'); + }); + + it('expect no time parameters if all are null', () => { + fetchGroundwaterLevels({ + site: '354133082042203', + parameterCode: '72019' + }); + const url = fakeServer.requests[0].url; + + expect(url).not.toContain('period'); + expect(url).not.toContain('startDT'); + expect(url).not.toContain('endDT'); + }); + it('Successful fetch returns a JSON object with ground water levels', () => { fakeServer.respondWith([200, {'Content-type': 'application/json'}, MOCK_GWLEVEL_DATA]); fetchPromise = fetchGroundwaterLevels(fetchParameters); diff --git a/assets/src/scripts/web-services/instantaneous-values.js b/assets/src/scripts/web-services/instantaneous-values.js index ce0c16ff653692a3e6527889271976dcfd1c9565..ca480fdd4395ac0b7b2504a58b61cf013374f924 100644 --- a/assets/src/scripts/web-services/instantaneous-values.js +++ b/assets/src/scripts/web-services/instantaneous-values.js @@ -22,14 +22,14 @@ function getNumberOfDays(period) { /** * Get a given time series dataset from Water Services. - * @param {Array} sites Array of site IDs to retrieve. - * @param {Array} parameterCodes Optional array of parameter codes + * @param {Array} sites Array of site IDs to retrieve. + * @param {String} parameterCodes * @param {String} period - ISO 8601 Duration * @param {String} startTime - ISO 8601 time * @param {String} endTime - ISO 8601 time * @return {Promise} resolves to an array of time series model object, rejects to an error */ -export const fetchTimeSeries = function({sites, parameterCode, period=null, startTime=null, endTime=null}) { +export const fetchTimeSeries = function({sites, parameterCode= null, period=null, startTime=null, endTime=null}) { let timeParams; let serviceRoot; @@ -57,4 +57,3 @@ export const fetchTimeSeries = function({sites, parameterCode, period=null, star throw reason; }); }; - diff --git a/assets/src/scripts/web-services/instantaneous-values.test.js b/assets/src/scripts/web-services/instantaneous-values.test.js index e6f9cad25b7a450406f95be7f69af2e4ea81e6b6..ddf1ac366f19885350bcf51abfd535c2375332a2 100644 --- a/assets/src/scripts/web-services/instantaneous-values.test.js +++ b/assets/src/scripts/web-services/instantaneous-values.test.js @@ -4,10 +4,10 @@ import sinon from 'sinon'; import config from 'ui/config'; -import {getPreviousYearTimeSeries, getTimeSeries, queryWeatherService} from './models'; +import {fetchTimeSeries} from './instantaneous-values'; -describe('Models module', () => { +describe('web-services/instantaneous-values', () => { let fakeServer; let restoreConsole; config.SERVICE_ROOT = 'https://fakeserviceroot.com'; @@ -24,141 +24,82 @@ describe('Models module', () => { }); describe('getTimeSeries function', () => { - const paramCode = '00060'; + const parameterCode = '00060'; const siteID = '05413500'; - it('Get url includes paramCds and sites', () => { - getTimeSeries({sites: [siteID], params: [paramCode]}); + it('Get url includes parameterCode and sites but no period or startDT/endDT', () => { + fetchTimeSeries({sites: [siteID], parameterCode: parameterCode}); let request = fakeServer.requests[0]; expect(request.url).toContain('sites=' + siteID); - expect(request.url).toContain('parameterCd=' + paramCode); - - getTimeSeries({sites: [siteID, '12345678'], params: [paramCode, '00080']}); - request = fakeServer.requests[1]; - - expect(request.url).toContain('sites=' + siteID + ',12345678'); - expect(request.url).toContain('parameterCd=' + paramCode + ',00080'); + expect(request.url).toContain('parameterCd=' + parameterCode); + expect(request.url).not.toContain('period'); + expect(request.url).not.toContain('startDT'); + expect(request.url).not.toContain('endDT'); }); - it('Get url includes has the default time period if startDate, endDate and period are null', () => { - getTimeSeries({sites: [siteID], params: [paramCode]}); + it('Get url includes has the time period if period is non null', () => { + fetchTimeSeries({sites: [siteID], parameterCode: parameterCode, period: 'P7D'}); const request = fakeServer.requests[0]; expect(request.url).toContain('period=P7D'); expect(request.url).not.toContain('startDT'); expect(request.url).not.toContain('endDT'); }); - it('Get url includes startDT and endDT when startDate and endDate are non-null', () => { - const startDate = new Date('2018-01-02T15:00:00.000-06:00'); - const endDate = new Date('2018-01-02T16:45:00.000-06:00'); - getTimeSeries({sites: [siteID], params: [paramCode], startDate: startDate, endDate: endDate}); + it('Get url includes startDT and endDT when startTime and endTime are non-null and period is null', () => { + const startTime = '2018-01-02T15:00:00.000-06:00'; + const endTime = '2018-01-02T16:45:00.000-06:00'; + fetchTimeSeries({sites: [siteID], parameterCode: parameterCode, period: null, startTime: startTime, endTime: endTime}); const request = fakeServer.requests[0]; - expect(request.url).not.toContain('period=P7D'); - expect(request.url).toContain('startDT=2018-01-02T21:00'); - expect(request.url).toContain('endDT=2018-01-02T22:45'); + expect(request.url).not.toContain('period'); + expect(request.url).toContain(`startDT=${startTime}`); + expect(request.url).toContain(`endDT=${endTime}`); }); - it('Get url includes period when available and startDT and endDT are null', () => { - getTimeSeries({ - sites: [siteID], - params: [paramCode], - period: 'P14D' - }); + it('Get url does not include any time parameters if all are null', () => { + fetchTimeSeries({sites: [siteID], parameterCode: parameterCode, period: null, startTime: null, endTime: null}); const request = fakeServer.requests[0]; - expect(request.url).toContain('period=P14D'); + expect(request.url).not.toContain('period'); expect(request.url).not.toContain('startDT'); expect(request.url).not.toContain('endDT'); }); it('Uses current data service root if data requested is less than 120 days old', () => { - getTimeSeries({sites: [siteID], params: [paramCode]}); + fetchTimeSeries({sites: [siteID], parameterCode: parameterCode}); let request = fakeServer.requests[0]; expect(request.url).toContain('https://fakeserviceroot.com'); - const startDate = new Date() - 110; - const endDate = new Date() - 10; - getTimeSeries({sites: [siteID], params: [paramCode], startDate: startDate, endDate: endDate}); + const startTime = DateTime.local().minus({days: 100}).toISO(); + const endTime = DateTime.local().minus({days: 10}).toISO(); + fetchTimeSeries({sites: [siteID], parameterCode: parameterCode, startTime: startTime, endTime: endTime}); request = fakeServer.requests[0]; expect(request.url).toContain('https://fakeserviceroot.com'); }); it('Uses nwis data service root if data requested is more than 120 days old', () => { - const startDate = new Date() - 121; - const endDate = new Date() - 10; - getTimeSeries({sites: [siteID], params: [paramCode], startDate: startDate, endDate: endDate}); + const startTime = DateTime.local().minus({days: 121}).toISO(); + const endTime = DateTime.local().minus({days: 10}).toISO(); + fetchTimeSeries({sites: [siteID], parameterCode: parameterCode, startTime: startTime, endTime: endTime}); let request = fakeServer.requests[0]; expect(request.url).toContain('https://pastfakeserviceroot.com'); }); it('Uses current data service root if period requested is less than P120D', () => { - getTimeSeries({sites: [siteID], params: [paramCode], period: 'P14D'}); + fetchTimeSeries({sites: [siteID], parameterCode: parameterCode, period: 'P14D'}); let request = fakeServer.requests[0]; expect(request.url).toContain('https://fakeserviceroot.com'); }); it('Uses past data service root if period requested is greater than P120D', () => { - getTimeSeries({sites: [siteID], params: [paramCode], period: 'P140D'}); + fetchTimeSeries({sites: [siteID], parameterCode: parameterCode, period: 'P140D'}); let request = fakeServer.requests[0]; expect(request.url).toContain('https://pastfakeserviceroot.com'); }); - }); - describe('getPreviousYearTimeSeries', () => { - const siteID = '05413500'; - - const startDate = 1514872800000; // milliSecond version of 01/02/2018 - const endDate = 1546408800000; // milliSecond version of 01/02/2019 - - it('Retrieves data using the startDT and endDT parameters', () => { - getPreviousYearTimeSeries({site: siteID, startTime: startDate, endTime: endDate}); + it('Contains no parameterCode is parameterCode is null', () => { + fetchTimeSeries({sites: [siteID], parameterCode: null, period: 'P14D'}); let request = fakeServer.requests[0]; - - expect(request.url).toContain('startDT=2017-01-02T06:00Z'); - expect(request.url).toContain('endDT=2018-01-02T06:00Z'); - }); - - it('Expects the difference between start and end dates for leap "compare" year will still be 365 days', () => { - const startDateLeapYearTest = 1483362000000; // milliSecond version of 01/02/2017 (year before a leap year, because we compare to the previous year) - const endDateLeapYearTest = 1514898000000; // milliSecond version of 01/02/2018 - getPreviousYearTimeSeries({site: siteID, startTime: startDateLeapYearTest, endTime: endDateLeapYearTest}); - const request = fakeServer.requests[fakeServer.requests.length - 1]; - - expect(request.url).toContain('startDT=2016-01-03T13:00Z'); - expect(request.url).toContain('endDT=2017-01-02T13:00Z'); - }); - }); - - describe('queryWeatherService', () => { - it('Expect the url to contain the latitude and longitude', () => { - queryWeatherService('45.3', '-100.2'); - - expect(fakeServer.requests[0].url).toContain('45.3,-100.2'); - }); - - it('Expect that a successful fetch returns the response', () => { - const MOCK_WEATHER_SERVICE_DATA = '{"properties" : {"timeZone" : "America/Chicago"}}'; - const promise = queryWeatherService('45.3', '100.2'); - fakeServer.requests[0].respond(200, {}, MOCK_WEATHER_SERVICE_DATA); - - return promise.then((response) => { - expect(response).toEqual({ - properties: { - timeZone: 'America/Chicago' - } - }); - }); - }); - - it('Expect that a failed fetch returns a JSON object with empty properties', () => { - const promise = queryWeatherService('45.3', '100.2'); - fakeServer.requests[0].respond(500, {}, 'Internal server error'); - - return promise.then((response) => { - expect(response).toEqual({ - properties: {} - }); - }); + expect(request.url).not.toContain('parameterCd'); }); }); }); diff --git a/wdfn-server/waterdata/tests/services/test_timezone.py b/wdfn-server/waterdata/tests/services/test_timezone.py new file mode 100644 index 0000000000000000000000000000000000000000..bd61cf5e3cc0f76ca3361f5ddd2cf5d29be8df0e --- /dev/null +++ b/wdfn-server/waterdata/tests/services/test_timezone.py @@ -0,0 +1,86 @@ +""" +Tests for timezone module +""" +import json +from unittest import mock + +from ...services.timezone import get_iana_time_zone + +MOCK_RESPONSE = """ +{"id": "https://api.weather.gov/points/38.9498,-77.1276", + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -77.127600000000001, + 38.949800000000003 + ] + }, + "properties": { + "@id": "https://api.weather.gov/points/38.9498,-77.1276", + "@type": "wx:Point", + "cwa": "LWX", + "forecastOffice": "https://api.weather.gov/offices/LWX", + "gridId": "LWX", + "gridX": 92, + "gridY": 72, + "forecast": "https://api.weather.gov/gridpoints/LWX/92,72/forecast", + "forecastHourly": "https://api.weather.gov/gridpoints/LWX/92,72/forecast/hourly", + "forecastGridData": "https://api.weather.gov/gridpoints/LWX/92,72", + "observationStations": "https://api.weather.gov/gridpoints/LWX/92,72/stations", + "relativeLocation": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -77.129442999999995, + 38.954484000000001 + ] + }, + "properties": { + "city": "Brookmont", + "state": "MD", + "distance": { + "value": 544.67498026369003, + "unitCode": "unit:m" + }, + "bearing": { + "value": 162, + "unitCode": "unit:degrees_true" + } + } + }, + "forecastZone": "https://api.weather.gov/zones/forecast/MDZ504", + "county": "https://api.weather.gov/zones/county/MDC031", + "fireWeatherZone": "https://api.weather.gov/zones/fire/MDZ504", + "timeZone": "America/New_York", + "radarStation": "KLWX" + } +} +""" + + +def test_good_weather_service_response(): + with mock.patch('waterdata.services.timezone.execute_get_request') as r_mock: + response = mock.Mock() + response.status_code = 200 + response.text = MOCK_RESPONSE + response.json.return_value = json.loads(MOCK_RESPONSE) + r_mock.return_value = response + + timezone = get_iana_time_zone('45.0', '-100.0') + r_mock.assert_called_once() + assert(r_mock.call_args.args[1] == 'points/45.0,-100.0') + assert(timezone == 'America/New_York') + + +def test_bad_weather_service_response(): + with mock.patch('waterdata.services.timezone.execute_get_request') as r_mock: + response = mock.Mock() + response.status_code = 500 + r_mock.return_value = response + + timezone = get_iana_time_zone('46.0', '-110.0') + r_mock.assert_called_once() + assert (r_mock.call_args.args[1] == 'points/46.0,-110.0') + assert timezone is None diff --git a/wdfn-server/waterdata/tests/test_location_utils.py b/wdfn-server/waterdata/tests/test_location_utils.py index 8e07e389898925a5d49465bbea6b52c0919d1714..5fd0cdfa6a2e293e8777728ccd715e5466b25e13 100644 --- a/wdfn-server/waterdata/tests/test_location_utils.py +++ b/wdfn-server/waterdata/tests/test_location_utils.py @@ -9,7 +9,7 @@ from pendulum import datetime from .. import app from ..location_utils import ( build_linked_data, get_disambiguated_values, get_state_abbreviation, rollup_dataseries, - get_period_of_record_by_parm_cd + get_period_of_record_by_parm_cd, get_default_parameter_code ) @@ -533,6 +533,7 @@ class TestRollupDataseries(TestCase): {'start_date', 'end_date', 'parameter_name', 'data_types', 'parameter_code'} ) + class TestGetPeriodOfRecordByParmCd(TestCase): def setUp(self): @@ -603,3 +604,47 @@ class TestGetPeriodOfRecordByParmCd(TestCase): 'end_date': '2019-03-01' } }) + + +class GetDefaultParameterCode(TestCase): + + def test_both_iv_and_gw(self): + self.assertEqual(get_default_parameter_code({ + '00010': {}, + '00060': {}, + '72019': {} + }, { + '72019': {}, + '65536': {} + }), '00060') + self.assertEqual(get_default_parameter_code({ + '00010', + '00011' + }, { + '72019' + }), '72019') + self.assertEqual(get_default_parameter_code({ + '00065', + '00010' + }, { + '72019': {} + }), '00065') + self.assertIn(get_default_parameter_code({ + '00010': {}, + '00011': {} + }, { + '65536': {} + }), ['00010', '00011']) + + def test_iv_only(self): + self.assertEqual(get_default_parameter_code({ + '00060', + '72019', + '00010' + }, {}), '00060') + + def test_gw_only(self): + self.assertEqual(get_default_parameter_code({}, { + '72019': {}, + '65536': {} + }), '72019') diff --git a/wdfn-server/waterdata/tests/test_utils.py b/wdfn-server/waterdata/tests/test_utils.py index 75a10c17571662671be1498a29ba1fb88e6e6a3d..046c39e8a70ee862ae5e4d3c7a8622ffc29dfd85 100644 --- a/wdfn-server/waterdata/tests/test_utils.py +++ b/wdfn-server/waterdata/tests/test_utils.py @@ -2,7 +2,7 @@ Unit tests for the main WDFN views. """ -from unittest import TestCase, mock, skip +from unittest import TestCase, mock from flask import Response @@ -34,8 +34,8 @@ class TestConstructUrl(TestCase): expected = 'https://fakeurl.gov/blah1/blah2' self.assertEqual(construct_url(self.test_netloc, self.test_path), expected) + class TestCreateMessage(TestCase): - @skip('Test fails on Macs') def test_create_message(self): target_email = 'test@test.com' form_data = { @@ -244,8 +244,7 @@ class TestParseRdb(TestCase): result = parse_rdb(iter(self.test_rdb_lines)) expected_1 = {'agency_cd': 'USGS', 'site_no': '345670', - 'station_nm': - 'Some Random Site', + 'station_nm': 'Some Random Site', 'site_tp_cd': 'ST', 'dec_lat_va': '200.94977778', 'dec_long_va': '-100.12763889', @@ -255,7 +254,7 @@ class TestParseRdb(TestCase): 'alt_acy_va': ' .1', 'alt_datum_cd': 'NAVD88', 'huc_cd': '02070010' - } + } expected_2 = {'agency_cd': 'USGS', 'site_no': '345671', 'station_nm': @@ -269,7 +268,7 @@ class TestParseRdb(TestCase): 'alt_acy_va': ' .1', 'alt_datum_cd': 'NAVD88', 'huc_cd': '02070010' - } + } self.assertDictEqual(next(result), expected_1) self.assertDictEqual(next(result), expected_2)