Skip to content
Snippets Groups Projects
Commit 4109244d authored by Bucknell, Mary S.'s avatar Bucknell, Mary S.
Browse files

Update changelog and linting

parent 8e3bb437
No related branches found
No related tags found
1 merge request!342Wdfn 740 - Replace components/hydrograph/parameters.js with a Vue component
......@@ -5,6 +5,9 @@ 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-1.2.0...master)
### Changed
- Refactored the parameter selection table component to use Vue.
## [1.2.0](https://github.com/usgs/waterdataui/compare/waterdataui-1.1.0...waterdataui-1.2.0) - 2022-06-10
### Added
- Feature toggle added was added for Affiliated Networks accordion.
......
......@@ -34,7 +34,6 @@ import {showDataIndicators} from './data-indicator';
import {drawDataTables} from './data-table';
import {initializeGraphBrush, drawGraphBrush} from './graph-brush';
import {drawTimeSeriesLegend} from './legend';
//import {drawSelectionList} from './parameters';
import {drawSelectActions} from './select-actions';
import {drawStatsTable} from './statistics-table';
import {drawTimeSpanShortcutButtons} from './time-span-shortcuts';
......@@ -44,6 +43,8 @@ import {initializeTooltipCursorSlider, drawTooltipCursorSlider} from './tooltip'
import GraphControlsApp from './GraphControlsApp.vue';
import ParameterSelectionApp from './ParameterSelectionApp.vue';
/* eslint-disable vue/one-component-per-file */
/*
* Renders the hydrograph on the node element using the Redux store for state information. The siteno, latitude, and
* longitude are required parameters. All others are optional and are used to set the initial state of the hydrograph.
......
import {select} from 'd3-selection';
import config from 'ui/config';
import {link} from 'ui/lib/d3-redux';
import {getInputsForRetrieval, getSelectedParameterCode} from 'ml/selectors/hydrograph-state-selector';
import {setSelectedParameterCode, setSelectedIVMethodID} from 'ml/store/hydrograph-state';
import {retrieveHydrographData} from 'ml/store/hydrograph-data';
import {getAvailableParameters} from './selectors/parameter-data';
import {getSortedIVMethods} from './selectors/time-series-data';
import {showDataIndicators} from './data-indicator';
import {drawMethodPicker} from './method-picker';
const ROW_TOGGLE = {
more: {
class: 'expansion-toggle expansion-toggle-more',
sprite: `${config.STATIC_URL}/img/sprite.svg#expand_more`
},
less: {
class: 'expansion-toggle expansion-toggle-less',
sprite: `${config.STATIC_URL}/img/sprite.svg#expand_less`
}
};
const ROW_TYPES = ['desktop', 'mobile'];
/*
* Sets the visibility of the expansion rows in selection to isExpanded.
* @param {D3 selection} selection - Can represent multiple expansion rows
* @param {Boolean} isExpanded
*/
const setExpansionRowVisibility = function(selection, isExpanded) {
selection.attr('hidden', isExpanded ? null : 'true');
};
/*
* Sets the state of the expansion toggle
* @param {D3 selection} selection - Can represent multiple expansion toggles
* @param {Boolean} isExpanded
*/
const setExpansionToggleState = function(selection, isExpanded) {
const toggleToUse = isExpanded ? ROW_TOGGLE.less : ROW_TOGGLE.more;
selection
.attr('class', toggleToUse.class)
.attr('aria-expanded', isExpanded ? 'true' : 'false')
.each(function() {
select(this).select('svg')
.attr('aria-hidden', isExpanded ? 'false' : 'true')
.select('use')
.attr('xlink:href', toggleToUse.sprite);
});
};
/*
* Helper function that adds the on click open and close functionality. Stopping event propagation is needed to prevent
* clicks on the containing element from changing this elements behavior.
* @param {D3 selection} container - the element to add the on click action
* @param {Object} parameter - Contains details about the current parameter code
* @param {String} type - Either 'desktop' or 'mobile'--indicates at what screen size the controls will show.
*/
const drawRowExpansionControl = function(container, parameter, type) {
// Don't show the open/close controls, if there is nothing in the expansion row, such as WaterAlert links.
if (parameter.waterAlert.hasWaterAlert) {
const expansionToggle = container
.append('span')
.attr('id', `expansion-toggle-${type}-${parameter.parameterCode}`);
expansionToggle.append('svg')
.attr('class', 'usa-icon')
.append('use').attr('xlink:href', ROW_TOGGLE.more.sprite);
setExpansionToggleState(expansionToggle, false);
expansionToggle.on('click', function(event) {
// Stop clicks on the toggle from triggering a change in selected parameter.
event.stopPropagation();
// Hide the expansion container on all rows on all rows except the clicked parameter row.
select('#select-time-series').selectAll('.expansion-container-row')
.filter(function() {
return this.id !== `expansion-container-row-${parameter.parameterCode}`;
})
.call(setExpansionRowVisibility, false);
select('#select-time-series').selectAll('.expansion-toggle ')
.filter(function() {
return !this.id.includes(parameter.parameterCode);
})
.call(setExpansionToggleState, false);
// Allow the user to change the expansion row visibility on the clicked parameter row. Both
// expansion toggles need to be updated
const selectedRow = select(`#container-row-${parameter.parameterCode}`);
const thisExpansionRow = selectedRow.select('.expansion-container-row');
const isExpanded = thisExpansionRow.attr('hidden') === 'true';
setExpansionRowVisibility(thisExpansionRow, isExpanded);
ROW_TYPES.forEach(typeOfRow => {
select(`#expansion-toggle-${typeOfRow}-${parameter.parameterCode}`)
.call(setExpansionToggleState, isExpanded);
});
});
}
};
/**
* Helper function that draws the main containing rows. Note the 'parameter selection' is a nested USWD grid.
* The grid has one 'container row' for each parameter (this function creates the 'container rows' for each parameter).
* As a side note - each container row will eventually contain three internal rows.
* 1) The 'Top Period Of Record Row' (shows only on mobile)
* 2) The 'Radio Button Row' (radio button and description show on mobile and desktop, toggle and period of record don't show mobile)
* 3) The 'Expansion Container Row' only shows when row clicked or toggled on. May act as a container for additional rows.
* @param {Object} container - The target element on which to append the row
* @param {Object} store - The application Redux state
* @param {String} siteno - A unique identifier for the monitoring location
* @param {String} agencyCode - Uniquely identifies the data provider, usually something like 'USGS'
* @param {String} parameterCode - five digit code that uniquely identifies the observed property (parameter)
* @param {String} parameterCode - USGS five digit parameter code
*/
const drawContainingRow = function(container, store, siteno, agencyCode, parameterCode) {
return container.append('div')
.attr('id', `container-row-${parameterCode}`)
.attr('class', 'grid-row-container-row')
.attr('ga-on', 'click')
.attr('ga-event-category', 'selectTimeSeries')
.attr('ga-event-action', `time-series-parmcd-${parameterCode}`)
.call(link(store, (container, selectedParameterCode) => {
container.classed('selected', parameterCode === selectedParameterCode)
.attr('aria-selected', parameterCode === selectedParameterCode);
}, getSelectedParameterCode))
.on('click', function(event) {
// Don't let clicks on the sampling method selections trigger a parameter reload.
event.stopPropagation();
// Get all of the other rows into the close orientation.
select('#select-time-series').selectAll('.expansion-container-row')
.call(setExpansionRowVisibility, false);
const expansionToggles = select('#select-time-series').selectAll('.expansion-toggle');
expansionToggles
.call(setExpansionToggleState, false);
// Show the clicked expansion row.
select(`#expansion-container-row-${parameterCode}`)
.call(setExpansionRowVisibility, true);
ROW_TYPES.forEach(typeOfIcon => {
select(`#expansion-toggle-${typeOfIcon}-${parameterCode}`)
.call(setExpansionToggleState, true);
});
// Change to the newly selected parameter both in the selection list ond on the graph.
const thisClass = select(this)
.attr('class');
if (!thisClass || !thisClass.includes('selected')) {
store.dispatch(setSelectedParameterCode(parameterCode));
showDataIndicators(true, store);
store.dispatch(retrieveHydrographData(siteno, agencyCode, getInputsForRetrieval(store.getState()), true))
.then(() => {
const sortedMethods = getSortedIVMethods(store.getState());
if (sortedMethods && sortedMethods.methods.length) {
store.dispatch(setSelectedIVMethodID(sortedMethods.methods[0].methodID));
}
showDataIndicators(false, store);
});
}
});
};
/**
* Helper function that creates the top row of each parameter selection. This row is hidden except on narrow screens
* and contains the period of record that appears above the parameter description.
* @param {D3 selection} container - The target element to append the row
* @param {Object} parameter - Contains details about the current parameter code
*/
const drawTopPeriodOfRecordRow = function(container, parameter) {
const gridRowInnerTopPeriodOfRecord = container.append('div')
.attr('class', 'grid-row-inner grid-row-period-of-record');
gridRowInnerTopPeriodOfRecord.append('div')
.attr('class', 'grid-row-period-of-record-text')
.text(`${parameter.periodOfRecord.begin_date} to ${parameter.periodOfRecord.end_date}`);
const topPeriodOfRecordRowExpansionControlDiv = gridRowInnerTopPeriodOfRecord.append('div')
.attr('class', 'toggle-for-top-period-of-record');
drawRowExpansionControl(topPeriodOfRecordRowExpansionControlDiv, parameter, 'mobile');
};
/**
* Helper function that draws the row containing the radio button and parameter description.
* @param {D3 selection} container - The target element to append the row
* @param {Object} parameter - Contains details about the current parameter code
* @param {Object} store - The application Redux state
*/
const drawRadioButtonRow = function(container, parameter, store) {
const gridRowInnerWithRadioButton = container.append('div')
.attr('class', 'grid-row grid-row-inner');
const radioButtonDiv = gridRowInnerWithRadioButton.append('div')
.attr('class', 'radio-button__param-select')
.append('div')
.attr('class', 'usa-radio');
radioButtonDiv.append('input')
.attr('class', 'usa-radio__input')
.attr('id', `radio-${parameter.parameterCode}`)
.attr('aria-labelledby', `radio-${parameter.parameterCode}-label`)
.attr('type', 'radio')
.attr('name', 'parameter-selection')
.attr('value', `${parameter.parameterCode}`)
.call(link(store, (inputElem, selectedParameterCode) => {
inputElem.property('checked', parameter.parameterCode === selectedParameterCode ? true : null);
}, getSelectedParameterCode));
radioButtonDiv.append('label')
.attr('class', 'usa-radio__label');
gridRowInnerWithRadioButton.append('div')
.attr('class', 'description__param-select')
.attr('id', `radio-${parameter.parameterCode}-label`)
.text(parameter.description);
const periodOfRecordToggleContainer = gridRowInnerWithRadioButton.append('div')
.attr('id', 'period-of-record-and-toggle-container')
.attr('class', 'period-of-record__param-select');
periodOfRecordToggleContainer.append('div')
.attr('id', 'period-of-record-text')
.attr('class', 'period-of-record__param-select')
.text(`${parameter.periodOfRecord.begin_date} to ${parameter.periodOfRecord.end_date}`);
const radioRowExpansionControlDiv = periodOfRecordToggleContainer.append('div')
.attr('class', 'toggle-radio_button_row');
drawRowExpansionControl(radioRowExpansionControlDiv, parameter, 'desktop');
};
/**
* Helper function that draws a row containing the controls for the WaterAlert subscription.
* @param {Object} container- The target element to append the row
* @param {String} siteno - A unique identifier for the monitoring location
* @param {D3 selection} parameter - Contains details about the current parameter code
*/
const drawWaterAlertRow = function(container, siteno, parameter) {
// WaterAlert doesn't accept the calculated parameter for Fahrenheit (such as 00010F), so we need to adjust the
// parameter code back to the Celsius version (such as 00010).
const waterAlertURL = function() {
return parameter.parameterCode.includes(config.CALCULATED_TEMPERATURE_VARIABLE_CODE) ?
`${config.WATERALERT_SUBSCRIPTION}/?site_no=${siteno}&parm=${parameter.parameterCode.replace(config.CALCULATED_TEMPERATURE_VARIABLE_CODE, '')}` :
`${config.WATERALERT_SUBSCRIPTION}/?site_no=${siteno}&parm=${parameter.parameterCode}`;
};
const gridRowInnerWaterAlert = container.append('div')
.attr('class', 'grid-row grid-row-inner');
gridRowInnerWaterAlert.append('div')
.attr('id', `wateralert-row-${parameter.parameterCode}`)
.attr('class', 'wateralert-row')
.append('a')
.attr('href', waterAlertURL())
.attr('target', '_blank')
.attr('class', 'water-alert-cell usa-tooltip')
.attr('data-position', 'left')
.attr('data-classes', 'width-full tablet:width-auto')
.attr('title', parameter.waterAlert.tooltipText)
.text(parameter.waterAlert.displayText);
};
/**
* A main function that creates the parameter selection list
* @param {Object} container - The target element to append the selection list
* @param {Object} store - The application Redux state
* @param {String} siteno - A unique identifier for the monitoring location
* @param {String} agencyCode - Usually an abbreviation that indicates the source of the data, most commonly 'USGS'
*/
export const drawSelectionList = function(container, store, siteno, agencyCode) {
const parameters = getAvailableParameters(store.getState());
const selectedParameter = getSelectedParameterCode(store.getState());
if (!Object.keys(parameters).length) {
return;
}
// Add the primary parameter selection container.
container.append('p')
.attr('id', 'parameter-selection-title')
.attr('class', 'usa-prose')
.text('Select Data to Graph');
const selectionList = container.append('div')
.attr('id', 'select-time-series')
.attr('class', 'main-parameter-selection-container')
.call(link(store, (selectionList, parameters) => {
selectionList.selectAll('.grid-row-container-row').remove();
parameters.forEach(parameter => {
// Add the main grid rows
const containerRow = drawContainingRow(selectionList, store, siteno, agencyCode, parameter.parameterCode);
// Add the nested grid rows
drawTopPeriodOfRecordRow(containerRow, parameter);
drawRadioButtonRow(containerRow, parameter, store);
// Add the expansion container in nested grid
const expansionContainerRow = containerRow.append('div')
.attr('id', `expansion-container-row-${parameter.parameterCode}`)
.attr('class', 'expansion-container-row')
.attr('hidden', 'true');
// Add the rows nested in the expansion container
if (parameter.waterAlert.hasWaterAlert) {
drawWaterAlertRow(expansionContainerRow, siteno, parameter);
}
// Expand the selected raw so users can see the Water Alert link and other things like sampling methods
if (parameter.parameterCode === selectedParameter) {
select(`#expansion-container-row-${selectedParameter}`)
.call(setExpansionRowVisibility, true);
ROW_TYPES.forEach(typeOfIcon => {
select(`#expansion-toggle-${typeOfIcon}-${selectedParameter}`)
.call(setExpansionToggleState, true);
});
}
});
}, getAvailableParameters));
// Draw method picker. This can only appear in the selected parameter row because
// we only know the methods for the currently selected data. Let the code figure out
// whether to draw it and where to put it whenever the sorted IV Methods change.
selectionList.call(link(store, drawMethodPicker, getSortedIVMethods, store));
};
import {select} from 'd3-selection';
import {enableFetchMocks, disableFetchMocks} from 'jest-fetch-mock';
import mockConsole from 'jest-mock-console';
import config from 'ui/config';
import * as utils from 'ui/utils';
import {configureStore} from 'ml/store';
import * as hydrographData from 'ml/store/hydrograph-data';
import * as dataIndicator from './data-indicator';
import {TEST_HYDROGRAPH_PARAMETERS} from './mock-hydrograph-state';
import {drawSelectionList} from './parameters';
describe('monitoring-location/components/hydrograph/parameters module', () => {
utils.mediaQuery = jest.fn().mockReturnValue(true);
const TEST_STATE = {
hydrographParameters: TEST_HYDROGRAPH_PARAMETERS,
hydrographState: {
selectedTimeSpan: 'P7D',
selectedParameterCode: '72019'
}
};
config.ivPeriodOfRecord = {
'00060': {
begin_date: '1980-01-01',
end_date: '2020-01-01'
},
'72019': {
begin_date: '1980-04-01',
end_date: '2020-04-01'
},
'00010': {
begin_date: '1981-04-01',
end_date: '2019-04-01'
}
};
config.gwPeriodOfRecord = {
'72019': {
begin_date: '1980-03-31',
end_date: '2020-03-31'
},
'62610': {
begin_date: '1980-05-01',
end_date: '2020-05-01'
}
};
let div;
let store;
let retrieveHydrographDataSpy;
let showDataIndicatorSpy;
let restoreConsole;
beforeAll(() => {
enableFetchMocks();
restoreConsole = mockConsole();
});
afterAll(() => {
disableFetchMocks();
restoreConsole();
});
beforeEach(() => {
div = select('body').append('div');
retrieveHydrographDataSpy = jest.spyOn(hydrographData, 'retrieveHydrographData');
showDataIndicatorSpy = jest.spyOn(dataIndicator, 'showDataIndicators');
});
afterEach(() => {
div.remove();
});
it('If no parameters defined the element is not rendered', () => {
store = configureStore({
hydrographParameters: {}
});
drawSelectionList(div, store, '11112222');
expect(div.select('#select-time-series').size()).toBe(0);
});
it('Expects the selection list to be rendered with the appropriate rows and selection', () => {
store = configureStore(TEST_STATE);
drawSelectionList(div, store, '11112222');
const container = div.select('#select-time-series');
expect(container.size()).toBe(1);
expect(container.selectAll('.grid-row-container-row').size()).toBe(5);
expect(container.selectAll('.expansion-toggle').size()).toBe(8); // Note - there are two for each parameter with expansion rows
expect(container.select('input:checked').attr('value')).toEqual('72019');
});
it('Expects changing the selection retrieves hydrograph data and statistics data', () => {
store = configureStore(TEST_STATE);
drawSelectionList(div, store, '11112222', 'USGS');
const rowOne = div.select('#container-row-00060');
rowOne.dispatch('click');
expect(store.getState().hydrographState.selectedParameterCode).toEqual('00060');
expect(showDataIndicatorSpy.mock.calls).toHaveLength(1);
expect(showDataIndicatorSpy.mock.calls[0][0]).toBe(true);
expect(retrieveHydrographDataSpy).toHaveBeenCalledWith('11112222', 'USGS', {
parameterCode: '00060',
period: 'P7D',
startTime: null,
endTime: null,
loadCompare: false
},
true // loadStats
);
});
it('Expects that the row for the selected parameter will be open when the page loads', function() {
store = configureStore({
...TEST_STATE,
hydrographState: {
...TEST_STATE.hydrographState,
selectedParameterCode: '00010'
}
});
drawSelectionList(div, store, '11112222');
expect(select('#expansion-container-row-00010').attr('hidden')).toBe(null);
expect(select('#expansion-container-row-72019').attr('hidden')).toBe('true');
});
it('Expects clicking on a row will expand and contract the correct rows', function() {
store = configureStore(TEST_STATE);
drawSelectionList(div, store, '11112222');
const firstTargetRow = div.select('#container-row-00010');
const secondTargetRow = div.select('#container-row-72019');
// When the page loads the selected parameter row will be open (in this case 72019)
expect(select('#expansion-container-row-00010').attr('hidden')).toBe('true');
expect(select('#expansion-container-row-72019').attr('hidden')).toBe(null);
firstTargetRow.dispatch('click');
expect(select('#expansion-container-row-00010').attr('hidden')).toBe(null);
expect(select('#expansion-container-row-72019').attr('hidden')).toBe('true');
secondTargetRow.dispatch('click');
expect(select('#expansion-container-row-00010').attr('hidden')).toBe('true');
expect(select('#expansion-container-row-72019').attr('hidden')).toBe(null);
});
it('Expects clicking the row toggle will expand the correct row and set the toggle', function() {
store = configureStore(TEST_STATE);
drawSelectionList(div, store, '11112222');
const firstToggleTarget = div.select('#expansion-toggle-desktop-00010');
const secondToggleTarget = div.select('#expansion-toggle-desktop-72019');
// When the page loads the selected parameter row will be open (not hidden), and rest closed
expect(firstToggleTarget.attr('aria-expanded')).toBe('false');
expect(select('#expansion-container-row-00010').attr('hidden')).toBe('true');
expect(select('#expansion-container-row-72019').attr('hidden')).toBe(null);
firstToggleTarget.dispatch('click');
// Clicking the row for 00010 will expand that row and close others
expect(firstToggleTarget.attr('aria-expanded')).toBe('true');
expect(select('#expansion-container-row-00010').attr('hidden')).toBe(null);
expect(select('#expansion-container-row-72019').attr('hidden')).toBe('true');
secondToggleTarget.dispatch('click');
expect(secondToggleTarget.attr('aria-expanded')).toBe('true');
expect(select('#expansion-container-row-00010').attr('hidden')).toBe('true');
expect(select('#expansion-container-row-72019').attr('hidden')).toBe(null);
secondToggleTarget.dispatch('click'); // click same target a second time
expect(secondToggleTarget.attr('aria-expanded')).toBe('false');
expect(select('#expansion-container-row-00010').attr('hidden')).toBe('true');
expect(select('#expansion-container-row-72019').attr('hidden')).toBe('true');
});
it('Expects parameters listed in config as having a WaterAlert will have a link in parameter list', function() {
store = configureStore(TEST_STATE);
drawSelectionList(div, store, '11112222');
const container = div.select('#select-time-series');
expect(container.select('#wateralert-row-00010').size()).toBe(1);
expect(container.select('#wateralert-row-00010').select('a').attr('href')).toContain('00010');
expect(container.select('#wateralert-row-00010').select('a').attr('href')).not.toContain('00010F');
});
it('Expects Celsius temperature parameters will have correct WaterAlert link in parameter list for the calculated version', function() {
// Note - WaterAlert only accepts the standard five digit USGS parameter code (such as 00010) not the calculated version like ('00010F').
store = configureStore(TEST_STATE);
drawSelectionList(div, store, '11112222');
const container = div.select('#select-time-series');
expect(container.select('#wateralert-row-00010F').size()).toBe(1);
expect(container.select('#wateralert-row-00010F').select('a').attr('href')).toContain('00010');
expect(container.select('#wateralert-row-00010F').select('a').attr('href')).not.toContain('00010F');
});
it('Expects that clicking a row toggle icon will set the icon to open and all others closed', function() {
store = configureStore(TEST_STATE);
drawSelectionList(div, store, '11112222');
const container = div.select('#select-time-series');
const clickedIconToggleOne = div.select('#expansion-toggle-desktop-72019');
const clickedIconToggleTwo = div.select('#expansion-toggle-desktop-00010F');
// test that the selected parameter's row is expanded
expect(container.selectAll('.expansion-toggle-less').size()).toBe(2);
expect(container.selectAll('.expansion-toggle-more').size()).toBe(6);
// test that a second click closes all previously selected icons
clickedIconToggleOne.dispatch('click');
expect(container.selectAll('.expansion-toggle-less').size()).toBe(0);
expect(container.selectAll('.expansion-toggle-more').size()).toBe(8);
// test that a click will open the row
clickedIconToggleTwo.dispatch('click');
expect(container.selectAll('.expansion-toggle-less').size()).toBe(2);
expect(container.selectAll('.expansion-toggle-more').size()).toBe(6);
});
});
......@@ -6,8 +6,8 @@
>
Sampling Methods/Sub-locations:
<span
class="usa-tooltip"
ref="tooltip"
class="usa-tooltip"
data-position="right"
title="The names used in dropdown menu are often specific to a particular monitoring location and describe sampling details used to distinguish time-series of the same type--examples include variations in physical location and sensor type."
>
......
......@@ -11,7 +11,7 @@
:aria-label="expansionIconLabel"
role="img"
>
<use :xlink:href="expansionIcon"/>
<use :xlink:href="expansionIcon" />
</svg>
</span>
</div>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment