diff --git a/CHANGELOG.md b/CHANGELOG.md index 065ec0f0dfe5ae06141077526265bf616fedc370..3096dd17bc8f9dadbf9e5e4cc02a7bffeadca0f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Added user email form for questions and comments. - Added field visit circles to brush. - Added field visits to the download data section. +- Added field visits to the Table of hydrograph data section. - Added form submit result page and form now uses smtp server. ### Changed 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 a31df88356a4be6b1e4c05c587c4d6bbd7313ee5..941ed547c6f405dbc501b590e31a832793ca5222 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/data-table.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/data-table.js @@ -1,33 +1,36 @@ import List from 'list.js'; +import {createStructuredSelector} from 'reselect'; import {link} from 'ui/lib/d3-redux'; +import {getVisibleGroundwaterLevelsTableData} from './selectors/discrete-data'; import {getCurrentPointData} from './selectors/drawing-data'; -const COLUMN_HEADINGS = [ - 'Parameter', - 'Time', - 'Result', - 'Approval', - 'Masks' -]; +const TABLE_CAPTION = { + iv: 'Instantaneous value data', + gw: 'Field visit data' +}; -const VALUE_NAMES = [ - 'parameterName', - 'dateTime', - 'result', - 'approvals', - 'masks' -]; +const COLUMN_HEADINGS = { + iv: ['Parameter', 'Time', 'Result', 'Approval', 'Masks'], + gw: ['Parameter', 'Time', 'Result'] +}; -const CONTAINER_ID = 'iv-data-table-container'; +const VALUE_NAMES = { + iv: ['parameterName', 'dateTime', 'result', 'approvals', 'masks'], + gw: ['parameterName', 'dateTime', 'result'] +}; -const drawTableBody = function(table, ivData) { - table.select('tbody').remove(); +const CONTAINER_ID = { + iv: 'iv-table-container', + gw: 'gw-table-container' +}; + +const drawTableBody = function(table, dataKind, data) { table.append('tbody') .classed('list', true); - const items = VALUE_NAMES.reduce(function(total, propName, index) { + const items = VALUE_NAMES[dataKind].reduce(function(total, propName, index) { if (index === 0) { return `${total}<th scope="row" class="${propName}"></th>`; } else { @@ -35,7 +38,7 @@ const drawTableBody = function(table, ivData) { } }, ''); const options = { - valueNames: VALUE_NAMES, + valueNames: VALUE_NAMES[dataKind], item: `<tr>${items}</tr>`, page: 30, pagination:[{ @@ -44,29 +47,59 @@ const drawTableBody = function(table, ivData) { right: 1 }] }; - new List(CONTAINER_ID, options, ivData); + new List(CONTAINER_ID[dataKind], options, data); }; /* - * Renders a table of the currently selected IV Data + * Renders a table of currentData * @param {D3 selection} elem - Table is rendered within elem - * @param {Redux store} store + * @param {String} kind - kind of data, iv or gw + * @param {Array of Object} currentData - data that will be rendered in the table. */ -export const drawDataTable = function(elem, store) { +const drawDataTable = function(elem, {dataKind, currentData}) { + elem.select(`#${CONTAINER_ID[dataKind]}`).remove(); + if (!currentData.length) { + return; + } + const tableContainer = elem.append('div') - .attr('id', CONTAINER_ID); + .attr('id', CONTAINER_ID[dataKind]); + const table = tableContainer.append('table') .classed('usa-table', true); tableContainer.append('ul') .classed('pagination', true); + table.append('caption') + .text(TABLE_CAPTION[dataKind]); table.append('thead') .append('tr') .selectAll('th') - .data(COLUMN_HEADINGS) + .data(COLUMN_HEADINGS[dataKind]) .enter() .append('th') .attr('scope', 'col') .text(col => col); - table.call(link(store, drawTableBody, getCurrentPointData)); + table.call(drawTableBody, dataKind, currentData); +}; + +/* + * Create the hydrograph data tables section which will update when the data + * displayed on the hydrograph changes. + * @param {D3 selection} elem + * @param {Redux store} store + */ +export const drawDataTables = function(elem, store) { + elem.append('div') + .attr('id', 'iv-hydrograph-data-table-container') + .call(link(store, drawDataTable, createStructuredSelector({ + dataKind: () => 'iv', + currentData: getCurrentPointData + }))); + elem.append('div') + .attr('id', 'gw-hydrograph-data-table-container') + .call(link(store, drawDataTable, createStructuredSelector({ + dataKind: () => 'gw', + currentData: getVisibleGroundwaterLevelsTableData + }))); }; \ No newline at end of file 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 186d5ed21c698817c039438a45b469a90a1b6d06..65bb01e1709c3c0815584b619e9eda999b2dcede 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 @@ -2,10 +2,21 @@ import {select} from 'd3-selection'; import {configureStore} from 'ml/store'; -import {drawDataTable} from './data-table'; +import {drawDataTables} from './data-table'; 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: '', @@ -96,6 +107,11 @@ const TEST_DATA = { 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' } } }, @@ -103,6 +119,34 @@ const TEST_DATA = { 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 + }] + } + } } }; @@ -112,9 +156,6 @@ describe('monitoring-location/components/hydrograph/data-table', () => { beforeEach(() => { testDiv = select('body').append('div'); - store = configureStore(TEST_DATA); - - drawDataTable(testDiv, store); }); afterEach(() => { @@ -122,7 +163,32 @@ describe('monitoring-location/components/hydrograph/data-table', () => { }); it('table with expected data', () => { + store = configureStore(TEST_DATA); + + 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); }); + + 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 + } + } + }; + store = configureStore(testData); + + 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); + }); }); \ No newline at end of file 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 1e2152cbd7c76ac5d04337b18d77ed0dcd0767d3..dff4a61aee2f09711c090b72685d2ca120ee4a90 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 @@ -475,7 +475,7 @@ describe('monitoring-location/components/hydrograph/download-links', () => { }); }); - fit('Renders the correct links when only groundwater data is available', () => { + it('Renders the correct links when only groundwater data is available', () => { const TEST_STATE = { 'ivTimeSeriesData': { 'queryInfo': { diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/index.js b/assets/src/scripts/monitoring-location/components/hydrograph/index.js index 86750f3c1ac1fdeefc044330fa49e79015f6103e..aef88f564b623a50462d9969d1d6ab6e95c58245 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/index.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/index.js @@ -24,7 +24,7 @@ import {Actions as timeZoneActions} from 'ml/store/time-zone'; import {Actions as floodDataActions} from 'ml/store/flood-inundation'; import {drawDateRangeControls} from './date-controls'; -import {drawDataTable} from './data-table'; +import {drawDataTables} from './data-table'; import {renderDownloadLinks} from './download-links'; import {drawGraphBrush} from './graph-brush'; import {drawGraphControls} from './graph-controls'; @@ -174,7 +174,7 @@ export const attachToNode = function(store, .call(renderDownloadLinks, store, siteno); nodeElem.select('#iv-data-table-container') - .call(drawDataTable, store); + .call(drawDataTables, store); //TODO: Find out why putting this before drawDataTable causes the tests to not work correctly nodeElem.select('.select-time-series-container') .call(link(store, plotSeriesSelectTable, createStructuredSelector({ 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 7dbc9856bc93842febe6c289fa495cf2c258944c..58c02f3bf45ba972c1181f721da80e976c6444c8 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/index.test.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/index.test.js @@ -15,7 +15,7 @@ import {attachToNode} from './index'; const TEST_STATE = { ivTimeSeriesData: { timeSeries: { - '00010:current': { + 'method1:00010:current': { points: [{ dateTime: 1514926800000, value: 4, @@ -25,7 +25,7 @@ const TEST_STATE = { tsKey: 'current:P7D', variable: '45807190' }, - '00060:current': { + 'method1:00060:current': { points: [{ dateTime: 1514926800000, value: 10, @@ -35,7 +35,7 @@ const TEST_STATE = { tsKey: 'current:P7D', variable: '45807197' }, - '00060:compare': { + 'method1:00060:compare': { points: [{ dateTime: 1514926800000, value: 10, @@ -531,10 +531,8 @@ describe('monitoring-location/components/hydrograph module', () => { ...TEST_STATE.ivTimeSeriesData, timeSeries: { ...TEST_STATE.ivTimeSeriesData.timeSeries, - '00060:current': { - ...TEST_STATE.ivTimeSeriesData.timeSeries['00060:current'], - startTime: 1514926800000, - endTime: 1514930400000, + 'method1:00060:current': { + ...TEST_STATE.ivTimeSeriesData.timeSeries['method1:00060:current'], points: [{ dateTime: 1514926800000, value: 10, @@ -652,15 +650,9 @@ describe('monitoring-location/components/hydrograph module', () => { expect(selectAll('table .usa-tooltip').size()).toBe(4); }); - it('should render the data table', () => { - return new Promise(resolve => { - window.requestAnimationFrame(() => { - const container = select('#iv-data-table-container'); - expect(container.selectAll('table').size()).toBe(1); - expect(container.select('.pagination').size()).toBe(1); - resolve(); - }); - }); + it('should have data tables for hydrograph data', () => { + expect(select('#iv-hydrograph-data-table-container').size()).toBe(1); + expect(select('#gw-hydrograph-data-table-container').size()).toBe(1); }); }); @@ -678,8 +670,8 @@ describe('monitoring-location/components/hydrograph module', () => { ...TEST_STATE.ivTimeSeriesData, timeSeries: { ...TEST_STATE.ivTimeSeriesData.timeSeries, - '00060:current': { - ...TEST_STATE.ivTimeSeriesData.timeSeries['00060:current'], + 'method1:00060:current': { + ...TEST_STATE.ivTimeSeriesData.timeSeries['method1:00060:current'], startTime: 1514926800000, endTime: 1514930400000, points: [{ 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 79582ac9c9636630959eef7dd53595cb409604a2..9702905d2442d0f8c82e974bc805d8cfc2a650c5 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/method-picker.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/method-picker.js @@ -26,7 +26,7 @@ export const drawMethodPicker = function(elem, store) { .on('change', function() { store.dispatch(Actions.setCurrentIVMethodID(parseInt(select(this).property('value')))); }) - .call(link(store,function(elem, {methods, currentMethodId}) { + .call(link(store, function(elem, {methods, currentMethodId}) { const currentMethodIdString = parseInt(currentMethodId); elem.selectAll('option').remove(); methods.forEach((method) => { 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 8703e139050794043904e8b5932930548472c987..85311a5bdb2376deac30fa8e527e9266f69155b4 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 @@ -1,7 +1,9 @@ +import {DateTime} from 'luxon'; import {createSelector} from 'reselect'; import {getIVCurrentVariableGroundwaterLevels} from 'ml/selectors/discrete-data-selector'; -import {getRequestTimeRange} from 'ml/selectors/time-series-selector'; +import {getRequestTimeRange, getCurrentVariable} from 'ml/selectors/time-series-selector'; +import {getIanaTimeZone} from 'ml/selectors/time-zone-selector'; /* * Returns a selector function that returns the groundwater levels that will be visible @@ -31,6 +33,32 @@ export const getVisibleGroundwaterLevelPoints = createSelector( } ); +/* + * Selector function which returns a function that returns an array of gw data appropriate + * for use in a table. + * @return {Function} - Function returns an array of visible ground water values with properties: + * @prop {String} parameterName + * @prop {String} result + * @prop {String} dateTime in site's time zone. + */ +export const getVisibleGroundwaterLevelsTableData = createSelector( + getCurrentVariable, + getVisibleGroundwaterLevelPoints, + getIanaTimeZone, + (currentVariable, gwLevels, timeZone) => { + return gwLevels.map((point) => { + return { + parameterName: currentVariable.variableName, + result: point.value.toString(), + dateTime: DateTime.fromMillis(point.dateTime, {zone: timeZone}).toISO({ + suppressMilliseconds: true, + suppressSeconds: true + }) + }; + }); + } +); + /* * Returns a selector function that returns true if any ground water * levels are visible. 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 cab712f84ce5c93853b7d0aaefbf69995f9b1ceb..bfdbdb239622fe39ba76c485b119431b214b72e0 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,4 +1,5 @@ -import {getVisibleGroundwaterLevelPoints, anyVisibleGroundwaterLevels} from './discrete-data'; +import {getVisibleGroundwaterLevelPoints, getVisibleGroundwaterLevelsTableData, + anyVisibleGroundwaterLevels} from './discrete-data'; describe('monitoring-location/components/hydrograph/selectors/discrete-data', () => { @@ -21,12 +22,14 @@ describe('monitoring-location/components/hydrograph/selectors/discrete-data', () '45807042': { variableCode: { 'value': '72019' - } + }, + variableName: 'Depth to water level' }, '45807041': { variableCode: { 'value': '00060' - } + }, + variableName: 'Streamflow' } } }, @@ -44,10 +47,10 @@ describe('monitoring-location/components/hydrograph/selectors/discrete-data', () oid: '45807042' }, values: [ - {value: '14.0', dateTime: 1491055200000}, - {value: '14.5', dateTime: 1490882400000}, + {value: '12.0', dateTime: 1489672800000}, {value: '13.0', dateTime: 1490536800000}, - {value: '12.0', dateTime: 1489672800000} + {value: '14.5', dateTime: 1490882400000}, + {value: '14.0', dateTime: 1491055200000} ] } } @@ -79,7 +82,56 @@ describe('monitoring-location/components/hydrograph/selectors/discrete-data', () 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 + }); + expect(result[1]).toEqual({ + value: 14.5, + dateTime: 1490882400000 + }); + }); + }); + + 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); + }); + + 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({ + parameterName: 'Depth to water level', + result: '13', + dateTime: '2017-03-26T09:00-05:00' + }); + expect(result[1]).toEqual({ + parameterName: 'Depth to water level', + result: '14.5', + dateTime: '2017-03-30T09:00-05:00' + }); }); }); diff --git a/wdfn-server/config.py b/wdfn-server/config.py index a846382cda788de5e4b7a7dc1de9781bb8511d79..c81876d5d2fcf5b3b6d4fcd5cfb9211ececeffc7 100644 --- a/wdfn-server/config.py +++ b/wdfn-server/config.py @@ -100,7 +100,7 @@ if os.getenv('CONTAINER_RUN', False): STATIC_ROOT = os.environ.get('STATIC_ROOT', '/static/') # Mail settings for feedback form -MAIL_SERVER = 'smtp1.usgs.gov' +MAIL_SERVER = 'smtp.usgs.gov' EMAIL_TARGET = { 'contact' : 'gs-w-{state_district_code}_NWISWeb_Data_Inquiries@usgs.gov', 'report': 'gs-w_support_nwisweb@usgs.gov', diff --git a/wdfn-server/waterdata/templates/macros/components.html b/wdfn-server/waterdata/templates/macros/components.html index b7f3c0e554011a3f813d99319317a615d3bc567f..8f06b73198717cd7194ba97a8992499dc86b7ecb 100644 --- a/wdfn-server/waterdata/templates/macros/components.html +++ b/wdfn-server/waterdata/templates/macros/components.html @@ -52,7 +52,7 @@ <button class="usa-accordion__button" aria-expanded="false" aria-controls="iv-data-table-container" ga-on="click" ga-event-category="accordion" ga-event-action="interactionWithHydrographDataTableAccordion"> - Table of hydrograph data + Hydrograph data table(s) </button> </h2> <div id="iv-data-table-container" class="usa-accordion__content usa-prose"></div> diff --git a/wdfn-server/waterdata/templates/monitoring_location.html b/wdfn-server/waterdata/templates/monitoring_location.html index 1132fad144320c76420237c700a09681423ef48f..08afab80013d5f49dbbe544bdb32315e4b1cd0f9 100644 --- a/wdfn-server/waterdata/templates/monitoring_location.html +++ b/wdfn-server/waterdata/templates/monitoring_location.html @@ -304,7 +304,7 @@ </main> <div class="user-feedback"> - <a href="{{ url_for('questions_comments', email_for_contact_about_data=email_for_contact_about_data) }}" + <a href="{{ url_for('questions_comments', email_for_data_questions=email_for_data_questions) }}" target="_blank" ga-on="click" ga-event-category="navigation" ga-event-action="toFeedBackForm"> <p class="usa-prose">Questions or Comments</p> diff --git a/wdfn-server/waterdata/templates/questions_comments.html b/wdfn-server/waterdata/templates/questions_comments.html index 473e3799a9ce6037753e131521697f45dcf657b2..ff852fa8259a2ed9749a5b56620923f3e900af4e 100644 --- a/wdfn-server/waterdata/templates/questions_comments.html +++ b/wdfn-server/waterdata/templates/questions_comments.html @@ -27,7 +27,7 @@ </div> </section> - <form class="usa-form" action="{{ url_for('questions_comments', email_for_contact_about_data=email_for_contact_about_data) }}" method="post" > + <form class="usa-form" action="{{ url_for('questions_comments', email_for_data_questions=email_for_data_questions) }}" method="post" > <fieldset id="feedback-destination-selection" class="usa-fieldset"> <legend class="usa-legend usa-legend">Let us help you to:</legend> <div class="usa-radio"> diff --git a/wdfn-server/waterdata/views.py b/wdfn-server/waterdata/views.py index ae48500fe280607b9dace3b43584cc12ecd0de0b..c2fa490c155f70230572592a4533de7009bc90f7 100644 --- a/wdfn-server/waterdata/views.py +++ b/wdfn-server/waterdata/views.py @@ -28,14 +28,14 @@ def home(): return render_template('index.html', version=__version__) -@app.route('/questions-comments/<email_for_contact_about_data>/', methods=["GET", "POST"]) -def questions_comments(email_for_contact_about_data): +@app.route('/questions-comments/<email_for_data_questions>/', methods=["GET", "POST"]) +def questions_comments(email_for_data_questions): """Render the user feedback form.""" referring_url = request.referrer user_system_data = request.user_agent.string if request.method == 'POST': - target_email = email_for_contact_about_data + target_email = email_for_data_questions if request.form['feedback-type'] != 'contact': target_email = app.config['EMAIL_TARGET'][request.form['feedback-type']] @@ -55,7 +55,7 @@ def questions_comments(email_for_contact_about_data): return render_template( 'questions_comments.html', - email_for_contact_about_data=email_for_contact_about_data, + email_for_data_questions=email_for_data_questions, monitoring_location_url=referring_url )