From 13ff76a7e4580ab8c0c3597c3fbb41c16e0f9a3b Mon Sep 17 00:00:00 2001
From: Daniel Naab <dnaab@usgs.gov>
Date: Wed, 28 Feb 2018 11:54:54 -0600
Subject: [PATCH] Handle median stat data in new normalized state structure.

---
 .../scripts/components/hydrograph/index.js    |  17 +--
 .../components/hydrograph/index.spec.js       |  33 ++++--
 .../scripts/components/hydrograph/legend.js   |  38 +++----
 .../components/hydrograph/legend.spec.js      |  49 +++++----
 .../scripts/components/hydrograph/store.js    |  36 +------
 .../components/hydrograph/store.spec.js       |  19 ----
 .../components/hydrograph/timeseries.js       |   5 +-
 .../scripts/components/hydrograph/tooltip.js  |   4 +-
 assets/src/scripts/models.js                  |  91 +++++++++++-----
 assets/src/scripts/models.spec.js             | 102 ++++++++++++++++--
 10 files changed, 247 insertions(+), 147 deletions(-)

diff --git a/assets/src/scripts/components/hydrograph/index.js b/assets/src/scripts/components/hydrograph/index.js
index 5f782e7c4..693c312e9 100644
--- a/assets/src/scripts/components/hydrograph/index.js
+++ b/assets/src/scripts/components/hydrograph/index.js
@@ -13,7 +13,7 @@ const { appendAxes, axesSelector } = require('./axes');
 const { ASPECT_RATIO_PERCENT, MARGIN, CIRCLE_RADIUS, layoutSelector } = require('./layout');
 const { drawSimpleLegend, legendDisplaySelector, createLegendMarkers } = require('./legend');
 const { plotSeriesSelectTable, availableTimeseriesSelector } = require('./parameters');
-const { pointsSelector, lineSegmentsSelector, isVisibleSelector, titleSelector, descriptionSelector, MASK_DESC } = require('./timeseries');
+const { currentVariableSelector, pointsSelector, lineSegmentsSelector, isVisibleSelector, titleSelector, descriptionSelector, MASK_DESC } = require('./timeseries');
 const { xScaleSelector, yScaleSelector } = require('./scales');
 const { Actions, configureStore } = require('./store');
 const { createTooltipFocus, createTooltipText } = require('./tooltip');
@@ -142,7 +142,7 @@ const plotLegend = function(elem, {displayItems, width, currentSegments, compare
 };
 
 
-const plotMedianPoints = function (elem, {visible, xscale, yscale, medianStatsData, showLabel}) {
+const plotMedianPoints = function (elem, {visible, xscale, yscale, points, showLabel, variable}) {
     elem.select('#median-points').remove();
 
     if (!visible) {
@@ -154,7 +154,7 @@ const plotMedianPoints = function (elem, {visible, xscale, yscale, medianStatsDa
             .attr('id', 'median-points');
 
     container.selectAll('medianPoint')
-        .data(medianStatsData)
+        .data(points)
         .enter()
         .append('circle')
             .attr('class', 'median-data-series')
@@ -171,11 +171,11 @@ const plotMedianPoints = function (elem, {visible, xscale, yscale, medianStatsDa
 
     if (showLabel) {
         container.selectAll('medianPointText')
-            .data(medianStatsData)
+            .data(points)
             .enter()
             .append('text')
                 .text(function(d) {
-                    return d.label;
+                    return `${d.value} ${variable.unit.unitCode}`;
                 })
                 .attr('x', function(d) {
                     return xscale(d.dateTime) + 5;
@@ -231,10 +231,11 @@ const timeSeriesGraph = function (elem) {
                     isCompareVisible: isVisibleSelector('compare')
                 })))
                 .call(link(plotMedianPoints, createStructuredSelector({
-                    visible: isVisibleSelector('medianStatistics'),
+                    visible: isVisibleSelector('median'),
                     xscale: xScaleSelector('current'),
                     yscale: yScaleSelector,
-                    medianStatsData: pointsSelector('medianStatistics'),
+                    points: pointsSelector('median'),
+                    variable: currentVariableSelector,
                     showLabel: (state) => state.showMedianStatsLabel
                 })));
 
@@ -265,7 +266,7 @@ const timeSeriesGraph = function (elem) {
                 (title) => [`Median ${title}`, 'Time']
             ),
             data: createSelector(
-                pointsSelector('medianStatistics'),
+                pointsSelector('median'),
                 points => points.map((value) => {
                     return [value.value, value.dateTime];
                 })
diff --git a/assets/src/scripts/components/hydrograph/index.spec.js b/assets/src/scripts/components/hydrograph/index.spec.js
index c416c02e1..818f04f39 100644
--- a/assets/src/scripts/components/hydrograph/index.spec.js
+++ b/assets/src/scripts/components/hydrograph/index.spec.js
@@ -25,6 +25,14 @@ const TEST_STATE = {
                     value: 10,
                     qualifiers: ['P']
                 }]
+            },
+            '00060:median': {
+                startTime: new Date('2018-01-02T15:00:00.000-06:00'),
+                endTime: new Date('2018-01-02T15:00:00.000-06:00'),
+                points: [{
+                    dateTime: new Date('2018-01-02T15:00:00.000-06:00'),
+                    value: 10
+                }]
             }
         },
         timeSeriesCollections: {
@@ -35,6 +43,10 @@ const TEST_STATE = {
             'coll2': {
                 variable: 45807197,
                 timeSeries: ['00060:compare']
+            },
+            'coll3': {
+                variable: 45807197,
+                timeSeries: ['00060:median']
             }
         },
         requests: {
@@ -43,6 +55,9 @@ const TEST_STATE = {
             },
             compare: {
                 timeSeriesCollections: ['coll2']
+            },
+            median: {
+                timeSeriesCollections: ['coll3']
             }
         },
         variables: {
@@ -58,7 +73,8 @@ const TEST_STATE = {
     currentVariableID: '45807197',
     showSeries: {
         current: true,
-        compare: true
+        compare: true,
+        median: true
     },
     width: 400
 };
@@ -172,20 +188,19 @@ describe('Hydrograph charting module', () => {
             expect(selectAll('g.current-mask-group').size()).toBe(1);
         });
 
-        xit('should have a point for the median stat data with a label', () => {
+        it('should have a point for the median stat data with a label', () => {
             expect(selectAll('svg #median-points circle.median-data-series').size()).toBe(1);
             expect(selectAll('svg #median-points text').size()).toBe(0);
         });
 
-        xit('should have a legend with two markers', () => {
-           expect(selectAll('g.legend-marker').size()).toBe(4);
+        it('should have a legend with six markers', () => {
+            // Current year, Current Mask, Last Year, Compare Mask, Median, Flood
+            expect(selectAll('g.legend-marker').size()).toBe(6);
         });
 
-        xit('show the labels for the median stat data showMedianStatsLabel is true', () => {
+        it('show the labels for the median stat data showMedianStatsLabel is true', () => {
             store.dispatch(Actions.showMedianStatsLabel(true));
-
             expect(selectAll('svg #median-points text').size()).toBe(1);
-
         });
     });
 
@@ -203,7 +218,7 @@ describe('Hydrograph charting module', () => {
             expect(selectAll('svg path.line').size()).toBe(2);
         });
 
-        xit('Should have three legend markers', () => {
+        it('Should have three legend markers', () => {
             expect(selectAll('g.legend-marker').size()).toBe(5);
         });
 
@@ -212,7 +227,7 @@ describe('Hydrograph charting module', () => {
             expect(selectAll('svg path.line').size()).toBe(1);
         });
 
-        xit('Should have two legend markers after the compare time series is removed', () => {
+        it('Should have two legend markers after the compare time series is removed', () => {
             store.dispatch(Actions.toggleTimeseries('compare', false));
             expect(selectAll('g.legend-marker').size()).toBe(3);
         });
diff --git a/assets/src/scripts/components/hydrograph/legend.js b/assets/src/scripts/components/hydrograph/legend.js
index 9efa17eaf..454a823a2 100644
--- a/assets/src/scripts/components/hydrograph/legend.js
+++ b/assets/src/scripts/components/hydrograph/legend.js
@@ -2,7 +2,7 @@
 const { createSelector } = require('reselect');
 const { defineLineMarker, defineCircleMarker, defineRectangleMarker, rectangleMarker } = require('./markers');
 const { CIRCLE_RADIUS } = require('./layout');
-const { MASK_DESC } = require('./timeseries');
+const { methodsSelector, timeSeriesSelector, MASK_DESC } = require('./timeseries');
 
 
 /**
@@ -118,7 +118,7 @@ const createLegendMarkers = function(dataPlotElements, lineSegments=[]) {
                 text = 'Current Year';
             }
             marker = defineLineMarker(domId, 'line', text, svgGroup);
-        } else if (dataItem === 'medianStatistics') {
+        } else if (dataItem === 'median') {
             text = 'Median';
             if (dataPlotElements.metadata.statistics.description) {
                 text = `${text} ${dataPlotElements.metadata.statistics.description}`;
@@ -157,30 +157,22 @@ const createLegendMarkers = function(dataPlotElements, lineSegments=[]) {
  */
 const legendDisplaySelector = createSelector(
     (state) => state.showSeries,
-    (state) => state.tsData,
-    (state) => state.currentParameterCode,
-    (showSeries, tsData, currentParameterCode) => {
-        const medianTS = tsData.medianStatistics[currentParameterCode] || {};
-        const statisticalMetaData = medianTS.medianMetadata || {};
-        let shownSeries = [];
-        let dataPlotElements = {};
-        for (let key in showSeries) {
-            if (showSeries.hasOwnProperty(key)) {
-                if (showSeries[key]) {
-                    shownSeries.push(key);
+    timeSeriesSelector('median'),
+    methodsSelector,
+    (showSeries, medianTSs, methods) => {
+        // FIXME: handle more than just the first median time series
+        const medianTS = medianTSs[0];
+        return {
+            dataItems: Object.keys(showSeries).filter(
+                key => showSeries.hasOwnProperty(key) && showSeries[key]),
+            metadata: {
+                statistics: {
+                    beginYear: medianTS ? medianTS.startTime.getFullYear() : undefined,
+                    endYear: medianTS ? medianTS.endTime.getFullYear() : undefined,
+                    description: medianTS && medianTS.method ? methods[medianTS.method].methodDescription : ''
                 }
             }
-        }
-
-        dataPlotElements.dataItems = shownSeries;
-        dataPlotElements.metadata = {
-            statistics: {
-                beginYear: statisticalMetaData.beginYear ? statisticalMetaData.beginYear : undefined,
-                endYear: statisticalMetaData.endYear ? statisticalMetaData.endYear : undefined,
-                description: medianTS.description || ''
-            }
         };
-        return dataPlotElements;
     }
 );
 
diff --git a/assets/src/scripts/components/hydrograph/legend.spec.js b/assets/src/scripts/components/hydrograph/legend.spec.js
index 03efb91bf..943b1f0ec 100644
--- a/assets/src/scripts/components/hydrograph/legend.spec.js
+++ b/assets/src/scripts/components/hydrograph/legend.spec.js
@@ -71,7 +71,7 @@ describe('Legend module', () => {
 
         it('should return markers for display', () => {
             let result = createLegendMarkers({
-                dataItems: ['current', 'medianStatistics'],
+                dataItems: ['current', 'median'],
                 metadata: {
                     statistics: {
                         beginYear: 2010,
@@ -109,7 +109,7 @@ describe('Legend module', () => {
 
         it('should line segment markers for display', () => {
             let result = createLegendMarkers({
-                dataItems: ['current', 'medianStatistics'],
+                dataItems: ['current', 'median'],
                 metadata: {
                     statistics: {
                         beginYear: 2010,
@@ -185,7 +185,7 @@ describe('Legend module', () => {
 
         it('should still work if stat begin and end years are absent', () => {
             let result = createLegendMarkers({
-                dataItems: ['medianStatistics'],
+                dataItems: ['median'],
                 metadata: {
                     statistics: {
                         beginYear: undefined,
@@ -201,42 +201,55 @@ describe('Legend module', () => {
 
         it('should return a marker if a time series is shown', () => {
             let result = legendDisplaySelector({
-                tsData: {
-                    medianStatistics: {
-                        '00060': {
-                            medianMetadata: {
-                                beginYear: 2010,
-                                endYear: 2012
-                            }
+                series: {
+                    timeSeries: {
+                        medianTS: {
+                            startTime: new Date('2010-10-10'),
+                            endTime: new Date('2012-10-10'),
+                            method: 'methodID',
+                            points: [1, 2, 3]
+                        }
+                    },
+                    methods: {
+                        methodID: {
+                            methodDescription: 'method description'
+                        }
+                    },
+                    timeSeriesCollections: {
+                        collectionID: {
+                            timeSeries: ['medianTS']
+                        }
+                    },
+                    requests: {
+                        median: {
+                            timeSeriesCollections: ['collectionID']
                         }
                     }
                 },
                 showSeries: {
                     current: true,
                     compare: false,
-                    medianStatistics: true
+                    median: true
                 },
                 currentParameterCode: '00060'
             });
             expect(result).toEqual({
-                dataItems: ['current', 'medianStatistics'],
+                dataItems: ['current', 'median'],
                 metadata: {
                     statistics: {
                         beginYear: 2010,
                         endYear: 2012,
-                        description: ''
+                        description: 'method description'
                     }
                 }
             });
         });
 
-        it('should not choke if statisticalMetadata years are absent', () => {
+        it('should not choke if median time series is absent', () => {
             let result = legendDisplaySelector({
-                tsData: {
-                    medianStatistics: {}
-                },
+                series: {},
                 showSeries: {
-                    medianStatistics: true
+                    median: true
                 }
             });
             expect(result.metadata.statistics).toEqual({
diff --git a/assets/src/scripts/components/hydrograph/store.js b/assets/src/scripts/components/hydrograph/store.js
index 6dc7c6f8c..d3b3e0ac5 100644
--- a/assets/src/scripts/components/hydrograph/store.js
+++ b/assets/src/scripts/components/hydrograph/store.js
@@ -40,14 +40,8 @@ export const Actions = {
                 const endTime = new Date(Math.max.apply(null,
                     tsArray.filter(ts => ts.endTime).map(ts => ts.endTime)));
 
-                /*const units = collection.timeSeries.reduce((units, series) => {
-                    units[series.code] = series.unit;
-                    return units;
-                }, {});*/
-                // FIXME: UNITS
-                const units = {};
-                let [plotableStats, newPlottableStats] = parseMedianData(stats, startTime, endTime, units);
-                dispatch(Actions.setMedianStatistics(plotableStats));
+                let medianCollection = parseMedianData(stats, startTime, endTime, collection.variables);
+                dispatch(Actions.addSeriesCollection('median', medianCollection));
             });
         };
     },
@@ -83,12 +77,6 @@ export const Actions = {
             key
         };
     },
-    setMedianStatistics(medianStatistics) {
-        return {
-            type: 'SET_MEDIAN_STATISTICS',
-            medianStatistics
-        };
-    },
     showMedianStatsLabel(show) {
         return {
             type: 'SHOW_MEDIAN_STATS_LABEL',
@@ -183,19 +171,6 @@ export const timeSeriesReducer = function (state={}, action) {
             delete newState.series.request[action.key];
             return newState;
 
-        case 'SET_MEDIAN_STATISTICS':
-            return {
-                ...state,
-                medianStatistics: {
-                    ...state.tsData['medianStatistics'],
-                    ...action.medianStatistics
-                },
-                showSeries: {
-                    ...state.showSeries,
-                    medianStatistics: true
-                }
-            };
-
         case 'SHOW_MEDIAN_STATS_LABEL':
             return {
                 ...state,
@@ -236,13 +211,10 @@ const MIDDLEWARES = [thunk];
 
 export const configureStore = function (initialState) {
     initialState = {
-        series: {},
         tsData: {
-            current: {
-            },
-            compare: {},
             medianStatistics: {}
         },
+        series: {},
         statisticalMetaData: {
             beginYear: '',
             endYear: ''
@@ -250,7 +222,7 @@ export const configureStore = function (initialState) {
         showSeries: {
             current: true,
             compare: false,
-            medianStatistics: false
+            median: false
         },
         currentParameterCode: null,
         currentVariableID: null,
diff --git a/assets/src/scripts/components/hydrograph/store.spec.js b/assets/src/scripts/components/hydrograph/store.spec.js
index 490c5b3a0..268f20e94 100644
--- a/assets/src/scripts/components/hydrograph/store.spec.js
+++ b/assets/src/scripts/components/hydrograph/store.spec.js
@@ -31,13 +31,6 @@ describe('Redux store', () => {
             });
         });
 
-        it('should create an action to reset a timeseries', () => {
-            expect(Actions.setMedianStatistics('statsData')).toEqual({
-                type: 'SET_MEDIAN_STATISTICS',
-                medianStatistics: 'statsData'
-            });
-        });
-
         it('should create an action to show the median stats label', () => {
             expect(Actions.showMedianStatsLabel(true)).toEqual({
                 type: 'SHOW_MEDIAN_STATS_LABEL',
@@ -125,18 +118,6 @@ describe('Redux store', () => {
             });
         });
 
-        xit('should handle SET_MEDIAN_STATISTICS', () => {
-            expect(timeSeriesReducer({series: {}}, {
-                type: 'SET_MEDIAN_STATISTICS',
-                medianStatistics: {medianData: 'here'}
-            })).toEqual({
-                medianStatistics: {medianData: 'here'},
-                showSeries: {
-                    medianStatistics: true
-                }
-            });
-        });
-
         it('should handle SHOW_MEDIAN_STATS_LABEL', () => {
             expect(timeSeriesReducer({}, {
                 type: 'SHOW_MEDIAN_STATS_LABEL',
diff --git a/assets/src/scripts/components/hydrograph/timeseries.js b/assets/src/scripts/components/hydrograph/timeseries.js
index dab29689a..08b718570 100644
--- a/assets/src/scripts/components/hydrograph/timeseries.js
+++ b/assets/src/scripts/components/hydrograph/timeseries.js
@@ -51,6 +51,9 @@ export const currentVariableSelector = createSelector(
 );
 
 
+export const methodsSelector = state => state.series.methods;
+
+
 /**
  * Returns a selector that, for a given tsKey:
  * Selects all time series.
@@ -67,7 +70,7 @@ export const timeSeriesSelector = memoize((tsKey, hasPoints=true) => createSelec
             const colSeries = collection.timeSeries.map(sID => timeSeries[sID]);
             Array.prototype.push.apply(seriesList, colSeries);
             return seriesList;
-        }, {});
+        }, []);
         if (hasPoints) {
             return series.filter(ts => ts.points.length > 0);
         } else {
diff --git a/assets/src/scripts/components/hydrograph/tooltip.js b/assets/src/scripts/components/hydrograph/tooltip.js
index 046b8db72..80c5a7ac8 100644
--- a/assets/src/scripts/components/hydrograph/tooltip.js
+++ b/assets/src/scripts/components/hydrograph/tooltip.js
@@ -107,7 +107,9 @@ const updateTooltipText = function(text, {datum, qualifiers, unitCode}) {
         if (!qualifiers) {
             return;
         }
-        const qualifierStr = Object.keys(qualifiers).map(key => qualifiers[key].qualifierDescription).join(', ');
+        const qualifierStr = Object.keys(qualifiers).filter(
+            key => datum.qualifiers.indexOf(key) > -1).map(
+                key => qualifiers[key].qualifierDescription).join(', ');
         const valueStr = `${datum.value || ''} ${datum.value ? unitCode : ''}`;
         label = `${valueStr} - ${formatTime(datum.dateTime)} (${qualifierStr})`;
         classes = classesForPoint(datum);
diff --git a/assets/src/scripts/models.js b/assets/src/scripts/models.js
index e510dae42..983b9909f 100644
--- a/assets/src/scripts/models.js
+++ b/assets/src/scripts/models.js
@@ -1,4 +1,5 @@
 const { utcFormat } = require('d3-time-format');
+
 const { get } = require('./ajax');
 const { deltaDays } = require('./utils');
 
@@ -102,10 +103,10 @@ export function isLeapYear(year) {
  * @param medianData
  * @param timeSeriesStartDateTime
  * @param timeSeriesEndDateTime
- * @param timeSeriesUnit
+ * @param varsByCode
  * @returns {object}
  */
-export function parseMedianTimeseries(medianData, timeSeriesStartDateTime, timeSeriesEndDateTime, timeSeriesUnit) {
+export function mergeMedianTimeseries(collection, medianData, timeSeriesStartDateTime, timeSeriesEndDateTime, varsByCode) {
     let values = [];
 
     let yearPresent = timeSeriesEndDateTime.getFullYear();
@@ -121,9 +122,8 @@ export function parseMedianTimeseries(medianData, timeSeriesStartDateTime, timeS
             recordDate = new Date(yearPrevious, month, day);
         }
         let median = {
-            time: recordDate,
-            value: parseFloat(medianDatum.p50_va),
-            label: `${medianDatum.p50_va} ${timeSeriesUnit}`
+            dateTime: recordDate,
+            value: parseFloat(medianDatum.p50_va)
         };
         // don't include leap days if it's not a leap year
         if (!isLeapYear(recordDate.getFullYear())) {
@@ -135,22 +135,54 @@ export function parseMedianTimeseries(medianData, timeSeriesStartDateTime, timeS
         }
     }
 
+    const tsId = `${medianData[0].parameter_cd}:${medianData[0].ts_id}:median`;
+    const tsCollectionId = `${medianData[0].site_no}:${medianData[0].parameter_cd}:median`;
+
+    // Normalize the median data into a structure comparable to how the
+    // IV service data is normalized.
     return {
-        id: medianData[0].ts_id,
-        code: medianData[0].parameter_cd,
-        name: medianData[0].loc_web_ds,
-        type: 'Statistic',
-        unit: timeSeriesUnit,
-        startTime: timeSeriesStartDateTime,
-        endTime: timeSeriesEndDateTime,
-        description: medianData[0].loc_web_ds,
-        medianMetadata: {
-            beginYear: medianData[0].begin_yr,
-            endYear: medianData[0].end_yr
+        ...collection,
+        timeSeries: {
+            ...collection.timeSeries || {},
+            [tsId]: {
+                points: values.sort(function (a, b) {
+                    return a.dateTime - b.dateTime;
+                }).slice(values.length - days, values.length),
+                startTime: timeSeriesStartDateTime,
+                endTime: timeSeriesEndDateTime,
+                tsKey: 'median',
+                method: tsId,
+                variable: varsByCode[medianData[0].parameter_cd].oid
+            }
+        },
+        timeSeriesCollections: {
+            ...collection.timeSeriesCollections || {},
+            [tsCollectionId]: {
+                sourceInfo: medianData[0].site_no,
+                variable: varsByCode[medianData[0].parameter_cd].oid,
+                name: tsCollectionId,
+                timeSeries: [
+                    ...((collection.timeSeriesCollections || {})[tsCollectionId] || []).timeSeries || [],
+                    tsId
+                ]
+            }
         },
-        values: values.sort(function(a, b){
-           return a.time - b.time;
-        }).slice(values.length - days, values.length)
+        methods: {
+            ...collection.methods || {},
+            [tsId]: {
+                methodDescription: medianData[0].loc_web_ds,
+                methodID: tsId
+            }
+        },
+        requests: {
+            ...collection.requests || {},
+            median: {
+                timeSeriesCollections: [
+                    ...((collection.requests || {}).median || {}).timeSeriesCollections || [],
+                    tsCollectionId
+                ]
+            }
+        }
     };
 }
 
@@ -162,7 +194,7 @@ export function parseMedianTimeseries(medianData, timeSeriesStartDateTime, timeS
  * @param timeSeriesUnit
  * @returns {object}
  */
-export function parseMedianData(medianData, timeSeriesStartDateTime, timeSeriesEndDateTime, timeSeriesUnits) {
+export function parseMedianData(medianData, timeSeriesStartDateTime, timeSeriesEndDateTime, variables) {
 
     // Organize median data by parameter code and timeseries id
     const dataByTimeseriesID = medianData.reduce(function (byTimeseriesID, d) {
@@ -171,19 +203,20 @@ export function parseMedianData(medianData, timeSeriesStartDateTime, timeSeriesE
         return byTimeseriesID;
     }, {});
 
-    const timeSeries = [];
+    const varsByCode = Object.keys(variables).reduce((vars, varId) => {
+        const variable = variables[varId];
+        vars[variable.variableCode.value] = variable;
+        return vars;
+    }, {});
+
+    let collection = {};
     for (let tsID of Object.keys(dataByTimeseriesID)) {
         const rows = dataByTimeseriesID[tsID];
-        const unit = timeSeriesUnits[rows[0].parameter_cd];
-        timeSeries.push(parseMedianTimeseries(rows, timeSeriesStartDateTime, timeSeriesEndDateTime, unit));
+        collection = mergeMedianTimeseries(
+            collection, rows, timeSeriesStartDateTime, timeSeriesEndDateTime, varsByCode);
     }
 
-    // FIXME: For a quick hack, only show a single set of median data per parameter code.
-    // Later, return the complete `timeSeries` list.
-    return [timeSeries.reduce(function (acc, series) {
-        acc[series.code] = series;
-        return acc;
-    }, {}), timeSeries];
+    return collection;
 }
 
 export function getPreviousYearTimeseries({site, startTime, endTime}) {
diff --git a/assets/src/scripts/models.spec.js b/assets/src/scripts/models.spec.js
index 2f018d11b..97cb2f413 100644
--- a/assets/src/scripts/models.spec.js
+++ b/assets/src/scripts/models.spec.js
@@ -1,6 +1,6 @@
 let proxyquire = require('proxyquireify')(require);
 
-const { parseRDB, parseMedianTimeseries, isLeapYear } = require('./models');
+const { isLeapYear, mergeMedianTimeseries, parseRDB } = require('./models');
 
 
 describe('Models module', () => {
@@ -152,15 +152,98 @@ describe('Models module', () => {
         const leapEndDate = new Date(2016, 2, 14);
 
         it('parseMedian data successfully constructs data for plotting', () => {
-            let result = parseMedianTimeseries(MOCK_MEDIAN_DATA, startDate, endDate, unit);
-            expect(result.values.length).toEqual(3);
-            expect(result.values[0]).toEqual({time: new Date(2017, 7, 5), value: 15, label: '15 ft3/s'});
+            const collection = mergeMedianTimeseries({}, MOCK_MEDIAN_DATA, startDate, endDate, MOCK_MEDIAN_VARIABLES);
+            expect(collection).toEqual({
+                timeSeries: {
+                    '00060:153885:median': {
+                        points: [{
+                            dateTime: new Date('Sat Aug 05 2017 00:00:00 GMT-0500 (CDT)'),
+                            value: 15
+                        }, {
+                            dateTime: new Date('Mon Jan 01 2018 00:00:00 GMT-0600 (CST)'),
+                            value: 16
+                        }, {
+                            dateTime: new Date('Sat Jan 13 2018 00:00:00 GMT-0600 (CST)'),
+                            value: 15
+                        }],
+                        startTime: new Date('Wed Jan 10 2018 00:00:00 GMT-0600 (CST)'),
+                        endTime: new Date('Sat Jan 13 2018 00:00:00 GMT-0600 (CST)'),
+                        tsKey: 'median',
+                        method: '00060:153885:median',
+                        variable: 'varID'
+                    }
+                },
+                timeSeriesCollections: {
+                    '05370000:00060:median': {
+                        sourceInfo: '05370000',
+                        variable: 'varID',
+                        name: '05370000:00060:median',
+                        timeSeries: [
+                            '00060:153885:median'
+                        ]
+                    }
+                },
+                methods: {
+                    '00060:153885:median': {
+                        methodDescription: '',
+                        methodID: '00060:153885:median'
+                    }
+                },
+                requests: {
+                    median: {
+                        timeSeriesCollections: ['05370000:00060:median']
+                    }
+                }
+            });
         });
 
         it('parseMedian data includes leap year when appropriate', () => {
-            let result = parseMedianTimeseries(MOCK_MEDIAN_DATA, leapStartDate, leapEndDate, unit);
-            expect(result.values.length).toEqual(4);
-            expect(result.values[3]).toEqual({time: new Date(2016, 1, 29), value: 13, label: '13 ft3/s'});
+            const collection = mergeMedianTimeseries({}, MOCK_MEDIAN_DATA, leapStartDate, leapEndDate, MOCK_MEDIAN_VARIABLES);
+            expect(collection).toEqual({
+                timeSeries: {
+                    '00060:153885:median': {
+                        points: [{
+                            dateTime: new Date('Wed Aug 05 2015 00:00:00 GMT-0500 (CDT)'),
+                            value: 15
+                        }, {
+                            dateTime: new Date('Fri Jan 01 2016 00:00:00 GMT-0600 (CST)'),
+                            value: 16
+                        }, {
+                            dateTime: new Date('Wed Jan 13 2016 00:00:00 GMT-0600 (CST)'),
+                            value: 15
+                        }, {
+                            dateTime: new Date('Mon Feb 29 2016 00:00:00 GMT-0600 (CST)'),
+                            value: 13
+                        }],
+                        startTime: new Date('Sun Jan 10 2016 00:00:00 GMT-0600 (CST)'),
+                        endTime: new Date('Mon Mar 14 2016 00:00:00 GMT-0500 (CDT)'),
+                        tsKey: 'median',
+                        method: '00060:153885:median',
+                        variable: 'varID'
+                    }
+                },
+                timeSeriesCollections: {
+                    '05370000:00060:median': {
+                        sourceInfo: '05370000',
+                        variable: 'varID',
+                        name: '05370000:00060:median',
+                        timeSeries: [
+                            '00060:153885:median'
+                        ]
+                    }
+                },
+                methods: {
+                    '00060:153885:median': {
+                        methodDescription: '',
+                        methodID: '00060:153885:median'
+                    }
+                },
+                requests: {
+                    median: {
+                        timeSeriesCollections: ['05370000:00060:median']
+                    }
+                }
+            });
         })
     });
 
@@ -3284,3 +3367,8 @@ const MOCK_MEDIAN_DATA = [
     {agency_cd: 'USGS', site_no: '05370000', parameter_cd: '00060', ts_id: '153885', loc_web_ds: '', month_nu: '8', day_nu: '5', begin_yr: '1969', end_yr: '2017', count_nu: '49', p50_va: '15'},
     {agency_cd: 'USGS', site_no: '05370000', parameter_cd: '00060', ts_id: '153885', loc_web_ds: '', month_nu: '2', day_nu: '29', begin_yr: '1969', end_yr: '2017', count_nu: '49', p50_va: '13'}
 ];
+const MOCK_MEDIAN_VARIABLES = {
+    '00060': {
+        oid: 'varID'
+    }
+}
-- 
GitLab