From aef9792c59c4b83fc310afa473f3d72f94b9baf9 Mon Sep 17 00:00:00 2001 From: Brandon Brown Date: Tue, 28 Mar 2017 16:34:58 -0500 Subject: [PATCH 01/20] Changes to linechart component to enable export of data. currently able to return the name of the selection (as seen in the select meters window) and what i believe to be meter data of some kind (still need to confirm that this is meter data and not some abstraction) --- src/client/app/components/LineChartComponent.jsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/client/app/components/LineChartComponent.jsx b/src/client/app/components/LineChartComponent.jsx index 7bdab845d..e4d60e0eb 100644 --- a/src/client/app/components/LineChartComponent.jsx +++ b/src/client/app/components/LineChartComponent.jsx @@ -53,12 +53,14 @@ const defaultConfig = { }] }; + export default class LineChartComponent extends React.Component { constructor(props) { super(props); this.onChartExtremesChange = this.onChartExtremesChange.bind(this); this.setupChartRef = this.setupChartRef.bind(this); + this.onExportClick = this.onExportClick.bind(this); this.state = { config: _.merge({}, defaultConfig, { xAxis: { events: { afterSetExtremes: this.onChartExtremesChange } } }) }; @@ -155,12 +157,25 @@ export default class LineChartComponent extends React.Component { this.props.fetchNewReadings(meterID, min, max); } } + onExportClick() { + const length = this.props.selectedMeters[0]; + const data = this.props.series[length].data; + const name = this.props.series[length].name; + data.forEach(function(currentValue) { + console.log(parseFloat(currentValue[0])); + }); + //console.log(data); + console.log(name); + //console.log(length); + } render() { return (
+
+ ); } } From d3bb015e22b19420d5c17e242909f5acfc26b36f Mon Sep 17 00:00:00 2001 From: Brandon Brown Date: Tue, 4 Apr 2017 18:34:59 -0500 Subject: [PATCH 02/20] Changes to linechart component to enable export of data. currently able to return the name of the selection (as seen in the select meters window) and what i believe to be meter data of some kind (still need to confirm that this is meter data and not some abstraction) --- src/client/app/components/LineChartComponent.jsx | 14 ++++++++------ src/client/app/containers/LineChartContainer.js | 1 + 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/client/app/components/LineChartComponent.jsx b/src/client/app/components/LineChartComponent.jsx index e4d60e0eb..a4de9c803 100644 --- a/src/client/app/components/LineChartComponent.jsx +++ b/src/client/app/components/LineChartComponent.jsx @@ -8,6 +8,7 @@ import React from 'react'; import ReactHighstock from 'react-highcharts/ReactHighstock'; import _ from 'lodash'; + const defaultConfig = { title: { text: null @@ -158,15 +159,16 @@ export default class LineChartComponent extends React.Component { } } onExportClick() { - const length = this.props.selectedMeters[0]; - const data = this.props.series[length].data; - const name = this.props.series[length].name; - data.forEach(function(currentValue) { + const meters = this.props.selectedMeters[0]; + const data = this.props.series[meters].data; + const name = this.props.series[meters].name; + /** data.forEach(function(currentValue) { console.log(parseFloat(currentValue[0])); - }); - //console.log(data); + }); */ + console.log(data); console.log(name); //console.log(length); + console.log(this.store.getState()); } render() { diff --git a/src/client/app/containers/LineChartContainer.js b/src/client/app/containers/LineChartContainer.js index 21036164a..4c24d1eb5 100644 --- a/src/client/app/containers/LineChartContainer.js +++ b/src/client/app/containers/LineChartContainer.js @@ -44,6 +44,7 @@ function mapStateToProps(state) { function mapDispatchToProps(dispatch) { return { fetchNewReadings: (meterID, startTimestamp, endTimestamp) => dispatch(fetchReadingsIfNeeded(meterID, startTimestamp, endTimestamp)) + exportReadings: (meterID, startTimestamp, endTimestamp) => dispatch(fetchReadingsIfNeeded(meterID, startTimestamp, endTimestamp)) }; } From e381bd2d7d5ce6258db895570e95dcd6a6ebf657 Mon Sep 17 00:00:00 2001 From: Brandon Brown Date: Tue, 4 Apr 2017 22:06:35 -0500 Subject: [PATCH 03/20] Changes to LineChartComponent, Readings.js in actions and LineChartContainer to enable the export of the state object --- src/client/app/actions/readings.js | 19 +++++++++++++++++++ .../app/components/LineChartComponent.jsx | 13 +++++++------ .../app/containers/LineChartContainer.js | 7 ++++--- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/client/app/actions/readings.js b/src/client/app/actions/readings.js index 010d6c82a..9f66490d0 100644 --- a/src/client/app/actions/readings.js +++ b/src/client/app/actions/readings.js @@ -61,3 +61,22 @@ export function fetchReadingsIfNeeded(meterID, startTimestamp, endTimestamp) { return Promise.resolve(); }; } + + function exportData(test){ + if (test === undefined){ + return false; + }else { + console.log(test); + return true; + } + } + +export function exportReadings(series){ + return (dispatch, getState) => { + if (exportData(getState())) { + console.log("resolved") + return Promise.resolve(); + } + return Promise.reject(); + }; + } diff --git a/src/client/app/components/LineChartComponent.jsx b/src/client/app/components/LineChartComponent.jsx index a4de9c803..4441f009d 100644 --- a/src/client/app/components/LineChartComponent.jsx +++ b/src/client/app/components/LineChartComponent.jsx @@ -159,16 +159,17 @@ export default class LineChartComponent extends React.Component { } } onExportClick() { - const meters = this.props.selectedMeters[0]; - const data = this.props.series[meters].data; - const name = this.props.series[meters].name; + //const meters = this.props.selectedMeters[0]; + //const data = this.props.series[meters].data; + //const name = this.props.series[meters].name; /** data.forEach(function(currentValue) { console.log(parseFloat(currentValue[0])); }); */ - console.log(data); - console.log(name); + //console.log(data); + //console.log(name); //console.log(length); - console.log(this.store.getState()); + //console.log(this.store.getState()); */ + this.props.exportReadings(); } render() { diff --git a/src/client/app/containers/LineChartContainer.js b/src/client/app/containers/LineChartContainer.js index 4c24d1eb5..3745c3a60 100644 --- a/src/client/app/containers/LineChartContainer.js +++ b/src/client/app/containers/LineChartContainer.js @@ -6,9 +6,10 @@ import { connect } from 'react-redux'; import LineChartComponent from '../components/LineChartComponent'; -import { fetchReadingsIfNeeded } from '../actions/readings'; +import { fetchReadingsIfNeeded, exportReadings } from '../actions/readings'; import { stringifyTimeInterval } from '../util'; + /** * @param {State} state */ @@ -43,8 +44,8 @@ function mapStateToProps(state) { function mapDispatchToProps(dispatch) { return { - fetchNewReadings: (meterID, startTimestamp, endTimestamp) => dispatch(fetchReadingsIfNeeded(meterID, startTimestamp, endTimestamp)) - exportReadings: (meterID, startTimestamp, endTimestamp) => dispatch(fetchReadingsIfNeeded(meterID, startTimestamp, endTimestamp)) + fetchNewReadings: (meterID, startTimestamp, endTimestamp) => dispatch(fetchReadingsIfNeeded(meterID, startTimestamp, endTimestamp)), + exportReadings: (series) => dispatch(exportReadings(series)) }; } From 5f9996a7b6addbecf824912854c155b43ff2c25e Mon Sep 17 00:00:00 2001 From: Paul Twomey Date: Tue, 4 Apr 2017 22:18:38 -0500 Subject: [PATCH 04/20] did stuff --- src/client/app/actions/readings.js | 21 +++++++++++++++++++ .../app/components/LineChartComponent.jsx | 11 +--------- .../app/containers/LineChartContainer.js | 6 +++--- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/client/app/actions/readings.js b/src/client/app/actions/readings.js index 010d6c82a..8cafb5520 100644 --- a/src/client/app/actions/readings.js +++ b/src/client/app/actions/readings.js @@ -53,6 +53,15 @@ function shouldFetchReadings(state, meterID, startTimestamp, endTimestamp) { return readingsForTimeInterval === undefined || !readingsForTimeInterval.isFetching; } +function testExport(stateSeries) { + if (stateSeries === undefined) { + return false; + } + console.log(stateSeries); + return true; + // return stateSeries === undefined; +} + export function fetchReadingsIfNeeded(meterID, startTimestamp, endTimestamp) { return (dispatch, getState) => { if (shouldFetchReadings(getState(), meterID, startTimestamp, endTimestamp)) { @@ -61,3 +70,15 @@ export function fetchReadingsIfNeeded(meterID, startTimestamp, endTimestamp) { return Promise.resolve(); }; } + +export function exportReadings(series) { + return (dispatch, getState) => { + if (testExport(getState())) { + console.log('resolved'); + return Promise.resolve(); + } + console.log('rejected'); + return Promise.reject(); + }; +} + diff --git a/src/client/app/components/LineChartComponent.jsx b/src/client/app/components/LineChartComponent.jsx index a4de9c803..d0dc69a46 100644 --- a/src/client/app/components/LineChartComponent.jsx +++ b/src/client/app/components/LineChartComponent.jsx @@ -159,16 +159,7 @@ export default class LineChartComponent extends React.Component { } } onExportClick() { - const meters = this.props.selectedMeters[0]; - const data = this.props.series[meters].data; - const name = this.props.series[meters].name; - /** data.forEach(function(currentValue) { - console.log(parseFloat(currentValue[0])); - }); */ - console.log(data); - console.log(name); - //console.log(length); - console.log(this.store.getState()); + this.props.exportReadings(this.props.series); } render() { diff --git a/src/client/app/containers/LineChartContainer.js b/src/client/app/containers/LineChartContainer.js index 4c24d1eb5..e2228d879 100644 --- a/src/client/app/containers/LineChartContainer.js +++ b/src/client/app/containers/LineChartContainer.js @@ -6,7 +6,7 @@ import { connect } from 'react-redux'; import LineChartComponent from '../components/LineChartComponent'; -import { fetchReadingsIfNeeded } from '../actions/readings'; +import { fetchReadingsIfNeeded, exportReadings } from '../actions/readings'; import { stringifyTimeInterval } from '../util'; /** @@ -43,8 +43,8 @@ function mapStateToProps(state) { function mapDispatchToProps(dispatch) { return { - fetchNewReadings: (meterID, startTimestamp, endTimestamp) => dispatch(fetchReadingsIfNeeded(meterID, startTimestamp, endTimestamp)) - exportReadings: (meterID, startTimestamp, endTimestamp) => dispatch(fetchReadingsIfNeeded(meterID, startTimestamp, endTimestamp)) + fetchNewReadings: (meterID, startTimestamp, endTimestamp) => dispatch(fetchReadingsIfNeeded(meterID, startTimestamp, endTimestamp)), + exportReadings: series => dispatch(exportReadings(series)) }; } From 950b04588063801b7313ef43fe68bace50792493 Mon Sep 17 00:00:00 2001 From: Brandon Brown Date: Wed, 5 Apr 2017 19:30:10 -0500 Subject: [PATCH 05/20] Able to access (compressed?) readings via the UIOptionsComponent. --- src/client/app/actions/readings.js | 22 ------------------- .../app/components/UIOptionsComponent.jsx | 5 +++++ .../app/containers/DashboardContainer.js | 2 +- .../app/containers/UIOptionsContainer.js | 17 +++++++++++++- 4 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src/client/app/actions/readings.js b/src/client/app/actions/readings.js index 271abc546..afc374c15 100644 --- a/src/client/app/actions/readings.js +++ b/src/client/app/actions/readings.js @@ -45,16 +45,6 @@ function fetchManyReadings(meterIDs, timeInterval) { }).then(response => dispatch(receiveManyReadings(meterIDs, timeInterval, response.data))); }; } - -function testExport(stateSeries) { - if (stateSeries === undefined) { - return false; - } - console.log(stateSeries); - return true; - // return stateSeries === undefined; -} - /** * Fetches readings for the given meterIDs if they are not already fetched or being fetched * @param {Array.} meterIDs @@ -71,15 +61,3 @@ export function fetchNeededReadings(meterIDs, timeInterval) { return Promise.resolve(); }; } - -export function exportReadings(series) { - return (dispatch, getState) => { - if (testExport(getState())) { - console.log('resolved'); - return Promise.resolve(); - } - console.log('rejected'); - return Promise.reject(); - }; -} - diff --git a/src/client/app/components/UIOptionsComponent.jsx b/src/client/app/components/UIOptionsComponent.jsx index c039a891d..69bc4a5d0 100644 --- a/src/client/app/components/UIOptionsComponent.jsx +++ b/src/client/app/components/UIOptionsComponent.jsx @@ -12,6 +12,7 @@ export default class UIOptionsComponent extends React.Component { constructor(props) { super(props); this.handleMeterSelect = this.handleMeterSelect.bind(this); + this.exportReading = this.exportReading.bind(this); } /** @@ -22,6 +23,9 @@ export default class UIOptionsComponent extends React.Component { this.props.fetchMetersDataIfNeeded(); } + exportReading() { + console.log(this.props.exportVals); + } handleMeterSelect(e) { e.preventDefault(); const options = e.target.options; @@ -93,6 +97,7 @@ export default class UIOptionsComponent extends React.Component {
+
); } diff --git a/src/client/app/containers/DashboardContainer.js b/src/client/app/containers/DashboardContainer.js index baaeef019..f0510eae3 100644 --- a/src/client/app/containers/DashboardContainer.js +++ b/src/client/app/containers/DashboardContainer.js @@ -6,6 +6,6 @@ import { connect } from 'react-redux'; import DashboardComponent from '../components/DashboardComponent'; /** - * Connects and passes down the Redux dispatch function to DashboardComponent + * Connects and passes down the Redux dispatch function to ExportComponent */ export default connect()(DashboardComponent); diff --git a/src/client/app/containers/UIOptionsContainer.js b/src/client/app/containers/UIOptionsContainer.js index 05a636e5f..ae18f3046 100644 --- a/src/client/app/containers/UIOptionsContainer.js +++ b/src/client/app/containers/UIOptionsContainer.js @@ -14,9 +14,24 @@ import { fetchMetersDataIfNeeded } from '../actions/meters'; */ function mapStateToProps(state) { const sortedMeters = _.sortBy(_.values(state.meters.byMeterID).map(meter => ({ id: meter.id, name: meter.name.trim() })), 'name'); + + const timeInterval = state.graph.timeInterval; + const data = { datasets: [] }; + for (const meterID of state.graph.selectedMeters) { + const readingsData = state.readings.byMeterID[meterID][timeInterval]; + //console.log(readingsData); + if (readingsData !== undefined && !readingsData.isFetching) { + data.datasets.push({ + label: state.meters.byMeterID[meterID].name, + id: state.meters.byMeterID[meterID], + exportVals: state.readings.byMeterID[meterID][timeInterval].readings.map(arr => ({ x: arr[0], y: arr[1] })) + }); + } + } //console.log(data); return { meters: sortedMeters, - selectedMeters: state.graph.selectedMeters + selectedMeters: state.graph.selectedMeters, + exportVals: data }; } From 1d565561c0c07af4f1bb6693ccaa3cde244db49f Mon Sep 17 00:00:00 2001 From: Brandon Brown Date: Thu, 6 Apr 2017 19:44:24 -0500 Subject: [PATCH 06/20] Able to format and download compressed meter data as a CSV file. --- src/client/app/actions/exportData.js | 48 +++++++++++++++++++ .../app/components/UIOptionsComponent.jsx | 8 +++- .../app/containers/UIOptionsContainer.js | 6 +-- 3 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 src/client/app/actions/exportData.js diff --git a/src/client/app/actions/exportData.js b/src/client/app/actions/exportData.js new file mode 100644 index 000000000..5ad72c973 --- /dev/null +++ b/src/client/app/actions/exportData.js @@ -0,0 +1,48 @@ +/** + * A function that converts the compressed meter data into a CSV formatted string. + * @param items The compressed meter data. + * @returns output A string containing the CSV formatted compressed meter data. + */ + +function convertToCSV(items) { + let csvOutput = 'id:,readings:\n'; + items.forEach(set => { + const data = set.exportVals; + const id = set.id; + let isStart = true; + data.forEach(reading => { + const info = reading.y; + if (isStart) { + csvOutput += `${id},${info} \n`; + isStart = false; + } else { + csvOutput += `${id},${info} \n`; + } + }); + }); + return csvOutput; +} +/** + * A function to download the formatted CSV file to the users computer. + * @param inputCSV A String containing the formatted CSV data. + */ +function downloadCSV(inputCSV) { + const csvContent = `data:text/csv;charset=utf-8,${inputCSV}`; + const encodedUri = encodeURI(csvContent); + const link = document.createElement('a'); + let fileName = window.prompt('Input a file name;', 'csvData'); + fileName += '.csv'; + link.setAttribute('href', encodedUri); + link.setAttribute('download', fileName); + document.body.appendChild(link); + + link.click(); // This will download the data file +} +/** + * Function to export compressed data from the graph currently displaying. May be used for routing if more export options are added + * @param dataSets An Object. The compressed data from each meter currently selected in the graph. + */ +export default function graphExport(dataSets) { + const dataToExport = convertToCSV(dataSets); + downloadCSV(dataToExport); +} diff --git a/src/client/app/components/UIOptionsComponent.jsx b/src/client/app/components/UIOptionsComponent.jsx index 69bc4a5d0..5059a9872 100644 --- a/src/client/app/components/UIOptionsComponent.jsx +++ b/src/client/app/components/UIOptionsComponent.jsx @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import React from 'react'; +import graphExport from '../actions/exportData'; export default class UIOptionsComponent extends React.Component { /** @@ -23,8 +24,13 @@ export default class UIOptionsComponent extends React.Component { this.props.fetchMetersDataIfNeeded(); } + /** + * Called when Export button is clicked. + * Passes an object containing the selected meter data to a function for export. + */ exportReading() { - console.log(this.props.exportVals); + const compressedData = this.props.exportVals.datasets; + graphExport(compressedData); } handleMeterSelect(e) { e.preventDefault(); diff --git a/src/client/app/containers/UIOptionsContainer.js b/src/client/app/containers/UIOptionsContainer.js index ae18f3046..e2af5a3c7 100644 --- a/src/client/app/containers/UIOptionsContainer.js +++ b/src/client/app/containers/UIOptionsContainer.js @@ -19,15 +19,15 @@ function mapStateToProps(state) { const data = { datasets: [] }; for (const meterID of state.graph.selectedMeters) { const readingsData = state.readings.byMeterID[meterID][timeInterval]; - //console.log(readingsData); if (readingsData !== undefined && !readingsData.isFetching) { data.datasets.push({ label: state.meters.byMeterID[meterID].name, - id: state.meters.byMeterID[meterID], + id: state.meters.byMeterID[meterID].id, + timestamp: state.readings.byMeterID[meterID][timeInterval].start_timestamp, exportVals: state.readings.byMeterID[meterID][timeInterval].readings.map(arr => ({ x: arr[0], y: arr[1] })) }); } - } //console.log(data); + } return { meters: sortedMeters, selectedMeters: state.graph.selectedMeters, From ff4f0059c24c61564e356c722eb8f7650f4e5ccf Mon Sep 17 00:00:00 2001 From: Brandon Brown Date: Fri, 7 Apr 2017 15:44:37 -0500 Subject: [PATCH 07/20] added timestamp to exported Data --- src/client/app/actions/exportData.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/client/app/actions/exportData.js b/src/client/app/actions/exportData.js index 5ad72c973..02be93488 100644 --- a/src/client/app/actions/exportData.js +++ b/src/client/app/actions/exportData.js @@ -1,3 +1,5 @@ +import moment from 'moment'; + /** * A function that converts the compressed meter data into a CSV formatted string. * @param items The compressed meter data. @@ -5,18 +7,19 @@ */ function convertToCSV(items) { - let csvOutput = 'id:,readings:\n'; + let csvOutput = 'id:,readings:,timestamp:\n'; items.forEach(set => { const data = set.exportVals; const id = set.id; let isStart = true; data.forEach(reading => { const info = reading.y; + const timeStamp = moment(reading.x).format('dddd MMM DD YYYY hh:mm a'); if (isStart) { - csvOutput += `${id},${info} \n`; + csvOutput += `${id},${info} kwh, ${timeStamp} \n`; isStart = false; } else { - csvOutput += `${id},${info} \n`; + csvOutput += `${id},${info} kwh, ${timeStamp} \n`; } }); }); From b6597572911954393af5fb01689db1729202c489 Mon Sep 17 00:00:00 2001 From: Brandon Brown Date: Wed, 12 Apr 2017 19:52:53 -0500 Subject: [PATCH 08/20] changed name of the exported file to "exportedDataOED" instead of prompting the user for a name. linted --- src/client/app/actions/exportData.js | 2 +- .../app/components/UIOptionsComponent.jsx | 4 +++- src/server/services/readMetasysData.js | 18 +++++++++--------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/client/app/actions/exportData.js b/src/client/app/actions/exportData.js index 02be93488..6141c3923 100644 --- a/src/client/app/actions/exportData.js +++ b/src/client/app/actions/exportData.js @@ -33,7 +33,7 @@ function downloadCSV(inputCSV) { const csvContent = `data:text/csv;charset=utf-8,${inputCSV}`; const encodedUri = encodeURI(csvContent); const link = document.createElement('a'); - let fileName = window.prompt('Input a file name;', 'csvData'); + let fileName = 'exportedDataOED'; fileName += '.csv'; link.setAttribute('href', encodedUri); link.setAttribute('download', fileName); diff --git a/src/client/app/components/UIOptionsComponent.jsx b/src/client/app/components/UIOptionsComponent.jsx index 5059a9872..930c78357 100644 --- a/src/client/app/components/UIOptionsComponent.jsx +++ b/src/client/app/components/UIOptionsComponent.jsx @@ -102,8 +102,10 @@ export default class UIOptionsComponent extends React.Component {
+
+ -
+ ); } diff --git a/src/server/services/readMetasysData.js b/src/server/services/readMetasysData.js index e3a96daac..848d7edc3 100644 --- a/src/server/services/readMetasysData.js +++ b/src/server/services/readMetasysData.js @@ -10,23 +10,23 @@ const Meter = require('./../models/Meter'); async function readMetasysData(filePath) { const readingArr = []; let i = 1; - //getting filename - const fileNameArray = filePath.split("/"); + // getting filename + const fileNameArray = filePath.split('/'); const fileName = fileNameArray.pop(); - //list of readings + // list of readings const rows = await readCsv(filePath); - //meterInformation + // meterInformation const meter = await Meter.getByName(fileName.replace('.csv', '')); for (const row of rows) { - //timestamp. end time stamp + // timestamp. end time stamp const timestamp = row[0].toLocaleString(); const start_timestamp = new Date(timestamp); - let end_timestamp = new Date(timestamp); - end_timestamp.setHours(end_timestamp.getHours()+1); + const end_timestamp = new Date(timestamp); + end_timestamp.setHours(end_timestamp.getHours() + 1); - //meterReading + // meterReading let meterReading = row[3]; meterReading = meterReading.replace('kWh', ''); meterReading = parseInt(meterReading); @@ -41,7 +41,7 @@ async function readMetasysData(filePath) { i++; } try { - //inserting all the data from an array into database and catching error when it occurs. + // inserting all the data from an array into database and catching error when it occurs. Reading.insertAll(readingArr); } catch (err) { console.error(err); From c3fc89f7a3ad8c708bc69a97370b458ba8b470b2 Mon Sep 17 00:00:00 2001 From: Brandon Brown Date: Wed, 12 Apr 2017 20:30:23 -0500 Subject: [PATCH 09/20] fixed a comment I accidentally changed in dashboardContainer --- src/client/app/containers/DashboardContainer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/app/containers/DashboardContainer.js b/src/client/app/containers/DashboardContainer.js index f0510eae3..baaeef019 100644 --- a/src/client/app/containers/DashboardContainer.js +++ b/src/client/app/containers/DashboardContainer.js @@ -6,6 +6,6 @@ import { connect } from 'react-redux'; import DashboardComponent from '../components/DashboardComponent'; /** - * Connects and passes down the Redux dispatch function to ExportComponent + * Connects and passes down the Redux dispatch function to DashboardComponent */ export default connect()(DashboardComponent); From 3ae6e546af46d387730b9d6de84951b3dd433d0a Mon Sep 17 00:00:00 2001 From: Brandon Brown Date: Sat, 15 Apr 2017 15:57:07 -0500 Subject: [PATCH 10/20] compressed filename variable in exportData.js to 1 line and removed unnecessary conditional. --- src/client/app/actions/exportData.js | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/client/app/actions/exportData.js b/src/client/app/actions/exportData.js index 6141c3923..712a77352 100644 --- a/src/client/app/actions/exportData.js +++ b/src/client/app/actions/exportData.js @@ -1,7 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + import moment from 'moment'; /** - * A function that converts the compressed meter data into a CSV formatted string. + * Function to converts the compressed meter data into a CSV formatted string. * @param items The compressed meter data. * @returns output A string containing the CSV formatted compressed meter data. */ @@ -11,30 +15,23 @@ function convertToCSV(items) { items.forEach(set => { const data = set.exportVals; const id = set.id; - let isStart = true; data.forEach(reading => { const info = reading.y; const timeStamp = moment(reading.x).format('dddd MMM DD YYYY hh:mm a'); - if (isStart) { - csvOutput += `${id},${info} kwh, ${timeStamp} \n`; - isStart = false; - } else { - csvOutput += `${id},${info} kwh, ${timeStamp} \n`; - } + csvOutput += `${id},${info} kwh, ${timeStamp} \n`; }); }); return csvOutput; } /** - * A function to download the formatted CSV file to the users computer. + * Function to download the formatted CSV file to the users computer. * @param inputCSV A String containing the formatted CSV data. */ function downloadCSV(inputCSV) { const csvContent = `data:text/csv;charset=utf-8,${inputCSV}`; const encodedUri = encodeURI(csvContent); const link = document.createElement('a'); - let fileName = 'exportedDataOED'; - fileName += '.csv'; + const fileName = 'exportedDataOED.csv'; link.setAttribute('href', encodedUri); link.setAttribute('download', fileName); document.body.appendChild(link); From 40dea5efca851d2367709012bad9c9849e8a6323 Mon Sep 17 00:00:00 2001 From: Brandon Brown Date: Sat, 15 Apr 2017 16:17:41 -0500 Subject: [PATCH 11/20] Moved exportData.js to new services directory. --- src/client/app/components/UIOptionsComponent.jsx | 2 +- src/client/app/{actions => services}/exportData.js | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/client/app/{actions => services}/exportData.js (100%) diff --git a/src/client/app/components/UIOptionsComponent.jsx b/src/client/app/components/UIOptionsComponent.jsx index 930c78357..2f57562c0 100644 --- a/src/client/app/components/UIOptionsComponent.jsx +++ b/src/client/app/components/UIOptionsComponent.jsx @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import React from 'react'; -import graphExport from '../actions/exportData'; +import graphExport from '../services/exportData'; export default class UIOptionsComponent extends React.Component { /** diff --git a/src/client/app/actions/exportData.js b/src/client/app/services/exportData.js similarity index 100% rename from src/client/app/actions/exportData.js rename to src/client/app/services/exportData.js From 9db2746f84d7944c088e958cc07c363bc1b5338b Mon Sep 17 00:00:00 2001 From: Brandon Brown Date: Sun, 23 Apr 2017 15:31:52 -0500 Subject: [PATCH 12/20] preparing to merge with master --- src/client/app/components/ExportComponent.jsx | 52 +++++++++++++++++++ src/client/app/containers/ExportContainer.js | 48 +++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 src/client/app/components/ExportComponent.jsx create mode 100644 src/client/app/containers/ExportContainer.js diff --git a/src/client/app/components/ExportComponent.jsx b/src/client/app/components/ExportComponent.jsx new file mode 100644 index 000000000..d467f7d90 --- /dev/null +++ b/src/client/app/components/ExportComponent.jsx @@ -0,0 +1,52 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import React from 'react'; +import graphExport from '../services/exportData'; + +export default class ExportComponent extends React.Component { + /** + * Initializes the component's state, binds all functions to 'this' UIOptionsComponent + * @param props The props passed down through the UIOptionsContainer + */ + constructor(props) { + super(props); + this.handleMeterSelect = this.handleMeterSelect.bind(this); + this.exportReading = this.exportReading.bind(this); + } + + /** + * Called when this component mounts + * Dispatches a Redux action to fetch meter information + */ + componentWillMount() { + this.props.fetchMetersDataIfNeeded(); + } + + /** + * Called when Export button is clicked. + * Passes an object containing the selected meter data to a function for export. + */ + exportReading() { + const compressedData = this.props.exportVals.datasets; + graphExport(compressedData); + } + + handleMeterSelect(e) { + e.preventDefault(); + const options = e.target.options; + const selectedMeters = []; + // We can't map here because this is a collection of DOM elements, not an array. + for (let i = 0; i < options.length; i++) { + if (options[i].selected) { + selectedMeters.push(parseInt(options[i].value)); + } + } + this.props.selectMeters(selectedMeters); + } + + render() { + return (); + } +} diff --git a/src/client/app/containers/ExportContainer.js b/src/client/app/containers/ExportContainer.js new file mode 100644 index 000000000..50a10655d --- /dev/null +++ b/src/client/app/containers/ExportContainer.js @@ -0,0 +1,48 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import _ from 'lodash'; +import { connect } from 'react-redux'; +import ExportComponent from '../components/ExportComponent'; +import { changeSelectedMeters } from '../actions/graph'; +import { fetchMetersDataIfNeeded } from '../actions/meters'; + +/** + * @param {State} state + * @return {{meterInfo: *, selectedMeters: Array}} + */ +function mapStateToProps(state) { + const sortedMeters = _.sortBy(_.values(state.meters.byMeterID).map(meter => ({ id: meter.id, name: meter.name.trim() })), 'name'); + + const timeInterval = state.graph.timeInterval; + const data = { datasets: [] }; + for (const meterID of state.graph.selectedMeters) { + const readingsData = state.readings.byMeterID[meterID][timeInterval]; + if (readingsData !== undefined && !readingsData.isFetching) { + data.datasets.push({ + label: state.meters.byMeterID[meterID].name, + id: state.meters.byMeterID[meterID].id, + timestamp: state.readings.byMeterID[meterID][timeInterval].start_timestamp, + exportVals: state.readings.byMeterID[meterID][timeInterval].readings.map(arr => ({ x: arr[0], y: arr[1] })) + }); + } + } + return { + meters: sortedMeters, + selectedMeters: state.graph.selectedMeters, + exportVals: data + }; +} + +function mapDispatchToProps(dispatch) { + return { + selectMeters: newSelectedMeterIDs => dispatch(changeSelectedMeters(newSelectedMeterIDs)), + fetchMetersDataIfNeeded: () => dispatch(fetchMetersDataIfNeeded()) + }; +} + +/** + * Connects changes to the Redux store to UIOptionsComponent via mapStateToProps + */ +export default connect(mapStateToProps, mapDispatchToProps)(ExportComponent); From 6c0937f918e3ffd2ef5246bf34c1173816556d78 Mon Sep 17 00:00:00 2001 From: Brandon Brown Date: Tue, 25 Apr 2017 15:54:37 -0500 Subject: [PATCH 13/20] Now works with the updated UIOptions component and barcharts. can currently only export data from linecharts (its the same data as shown on the bar chart however it may include extra data points that were not included on the bar charts. --- src/client/app/components/ExportComponent.jsx | 24 +---------------- src/client/app/components/HomeComponent.jsx | 3 +++ src/client/app/containers/ExportContainer.js | 21 +++------------ src/client/app/services/exportData.js | 27 ++++++++++--------- 4 files changed, 22 insertions(+), 53 deletions(-) diff --git a/src/client/app/components/ExportComponent.jsx b/src/client/app/components/ExportComponent.jsx index d467f7d90..edeb633d9 100644 --- a/src/client/app/components/ExportComponent.jsx +++ b/src/client/app/components/ExportComponent.jsx @@ -7,23 +7,14 @@ import graphExport from '../services/exportData'; export default class ExportComponent extends React.Component { /** - * Initializes the component's state, binds all functions to 'this' UIOptionsComponent + * Initializes the component's state, binds all functions to 'this' ExportComponent * @param props The props passed down through the UIOptionsContainer */ constructor(props) { super(props); - this.handleMeterSelect = this.handleMeterSelect.bind(this); this.exportReading = this.exportReading.bind(this); } - /** - * Called when this component mounts - * Dispatches a Redux action to fetch meter information - */ - componentWillMount() { - this.props.fetchMetersDataIfNeeded(); - } - /** * Called when Export button is clicked. * Passes an object containing the selected meter data to a function for export. @@ -33,19 +24,6 @@ export default class ExportComponent extends React.Component { graphExport(compressedData); } - handleMeterSelect(e) { - e.preventDefault(); - const options = e.target.options; - const selectedMeters = []; - // We can't map here because this is a collection of DOM elements, not an array. - for (let i = 0; i < options.length; i++) { - if (options[i].selected) { - selectedMeters.push(parseInt(options[i].value)); - } - } - this.props.selectMeters(selectedMeters); - } - render() { return (); } diff --git a/src/client/app/components/HomeComponent.jsx b/src/client/app/components/HomeComponent.jsx index c53f049df..8a2de586f 100644 --- a/src/client/app/components/HomeComponent.jsx +++ b/src/client/app/components/HomeComponent.jsx @@ -5,6 +5,8 @@ import React from 'react'; import HeaderComponent from './HeaderComponent'; import DashboardContainer from '../containers/DashboardContainer'; +import ExportContainer from '../containers/ExportContainer'; + /** * Top-level React component that controls the home page @@ -15,6 +17,7 @@ export default function HomeComponent() {
+
); } diff --git a/src/client/app/containers/ExportContainer.js b/src/client/app/containers/ExportContainer.js index 50a10655d..46982c572 100644 --- a/src/client/app/containers/ExportContainer.js +++ b/src/client/app/containers/ExportContainer.js @@ -2,47 +2,34 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import _ from 'lodash'; import { connect } from 'react-redux'; import ExportComponent from '../components/ExportComponent'; -import { changeSelectedMeters } from '../actions/graph'; -import { fetchMetersDataIfNeeded } from '../actions/meters'; /** * @param {State} state * @return {{meterInfo: *, selectedMeters: Array}} */ function mapStateToProps(state) { - const sortedMeters = _.sortBy(_.values(state.meters.byMeterID).map(meter => ({ id: meter.id, name: meter.name.trim() })), 'name'); - const timeInterval = state.graph.timeInterval; const data = { datasets: [] }; for (const meterID of state.graph.selectedMeters) { - const readingsData = state.readings.byMeterID[meterID][timeInterval]; + const readingsData = state.readings.line.byMeterID[meterID][timeInterval]; if (readingsData !== undefined && !readingsData.isFetching) { data.datasets.push({ label: state.meters.byMeterID[meterID].name, id: state.meters.byMeterID[meterID].id, - timestamp: state.readings.byMeterID[meterID][timeInterval].start_timestamp, - exportVals: state.readings.byMeterID[meterID][timeInterval].readings.map(arr => ({ x: arr[0], y: arr[1] })) + timestamp: state.readings.line.byMeterID[meterID][timeInterval].start_timestamp, + exportVals: state.readings.line.byMeterID[meterID][timeInterval].readings.map(arr => ({ x: arr[0], y: arr[1] })) }); } } return { - meters: sortedMeters, selectedMeters: state.graph.selectedMeters, exportVals: data }; } -function mapDispatchToProps(dispatch) { - return { - selectMeters: newSelectedMeterIDs => dispatch(changeSelectedMeters(newSelectedMeterIDs)), - fetchMetersDataIfNeeded: () => dispatch(fetchMetersDataIfNeeded()) - }; -} - /** * Connects changes to the Redux store to UIOptionsComponent via mapStateToProps */ -export default connect(mapStateToProps, mapDispatchToProps)(ExportComponent); +export default connect(mapStateToProps)(ExportComponent); diff --git a/src/client/app/services/exportData.js b/src/client/app/services/exportData.js index 712a77352..27be312df 100644 --- a/src/client/app/services/exportData.js +++ b/src/client/app/services/exportData.js @@ -11,14 +11,14 @@ import moment from 'moment'; */ function convertToCSV(items) { - let csvOutput = 'id:,readings:,timestamp:\n'; + let csvOutput = 'Label,Readings,Timestamp\n'; items.forEach(set => { const data = set.exportVals; - const id = set.id; + const label = set.label; data.forEach(reading => { const info = reading.y; const timeStamp = moment(reading.x).format('dddd MMM DD YYYY hh:mm a'); - csvOutput += `${id},${info} kwh, ${timeStamp} \n`; + csvOutput += `${label},${info} kwh, ${timeStamp}\n`; }); }); return csvOutput; @@ -27,16 +27,17 @@ function convertToCSV(items) { * Function to download the formatted CSV file to the users computer. * @param inputCSV A String containing the formatted CSV data. */ -function downloadCSV(inputCSV) { - const csvContent = `data:text/csv;charset=utf-8,${inputCSV}`; - const encodedUri = encodeURI(csvContent); - const link = document.createElement('a'); - const fileName = 'exportedDataOED.csv'; - link.setAttribute('href', encodedUri); - link.setAttribute('download', fileName); - document.body.appendChild(link); +function downloadCSV(inputCSV, fileName) { + const element = document.createElement('a'); + element.setAttribute('href', `data:text/csv;charset=utf-8,${encodeURIComponent(inputCSV)}`); + element.setAttribute('download', fileName); - link.click(); // This will download the data file + element.style.display = 'none'; + document.body.appendChild(element); + + element.click(); + + document.body.removeChild(element); } /** * Function to export compressed data from the graph currently displaying. May be used for routing if more export options are added @@ -44,5 +45,5 @@ function downloadCSV(inputCSV) { */ export default function graphExport(dataSets) { const dataToExport = convertToCSV(dataSets); - downloadCSV(dataToExport); + downloadCSV(dataToExport, 'test.csv'); } From dc03958f2ca1db596a9d0272842d1cac7957407f Mon Sep 17 00:00:00 2001 From: Brandon Brown Date: Tue, 25 Apr 2017 16:36:53 -0500 Subject: [PATCH 14/20] Successfully exports the displayed data from bar charts (for full data set export from linechart) timestamps for bar chart exports don't currently display correctly --- src/client/app/containers/ExportContainer.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/client/app/containers/ExportContainer.js b/src/client/app/containers/ExportContainer.js index 46982c572..544140b3c 100644 --- a/src/client/app/containers/ExportContainer.js +++ b/src/client/app/containers/ExportContainer.js @@ -12,15 +12,28 @@ import ExportComponent from '../components/ExportComponent'; function mapStateToProps(state) { const timeInterval = state.graph.timeInterval; const data = { datasets: [] }; + let readingsData; + const chart = state.graph.chartToRender; + const barDuration = state.graph.barDuration; + for (const meterID of state.graph.selectedMeters) { - const readingsData = state.readings.line.byMeterID[meterID][timeInterval]; - if (readingsData !== undefined && !readingsData.isFetching) { + if (chart === 'line') { + readingsData = state.readings.line.byMeterID[meterID][timeInterval]; + } else { readingsData = state.readings.bar.byMeterID[meterID][timeInterval][barDuration]; } + if (readingsData !== undefined && !readingsData.isFetching && chart === 'line') { data.datasets.push({ label: state.meters.byMeterID[meterID].name, id: state.meters.byMeterID[meterID].id, timestamp: state.readings.line.byMeterID[meterID][timeInterval].start_timestamp, exportVals: state.readings.line.byMeterID[meterID][timeInterval].readings.map(arr => ({ x: arr[0], y: arr[1] })) }); + } else if (readingsData !== undefined && !readingsData.isFetching && chart === 'bar') { + data.datasets.push({ + label: state.meters.byMeterID[meterID].name, + id: state.meters.byMeterID[meterID].id, + timestamp: state.readings.bar.byMeterID[meterID][timeInterval][barDuration].start_timestamp, + exportVals: state.readings.bar.byMeterID[meterID][timeInterval][barDuration].readings.map(arr => ({ x: arr[0], y: arr[1] })) + }); } } return { From 538b85cfef3a24e5f525770b30513fe646fd364c Mon Sep 17 00:00:00 2001 From: Brandon Brown Date: Tue, 25 Apr 2017 17:43:31 -0500 Subject: [PATCH 15/20] Successfully exports the displayed data from bar charts (for full data set export from linechart) timestamps for bar chart exports don't currently display correctly --- src/client/app/containers/ExportContainer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/app/containers/ExportContainer.js b/src/client/app/containers/ExportContainer.js index 544140b3c..3f0d20d63 100644 --- a/src/client/app/containers/ExportContainer.js +++ b/src/client/app/containers/ExportContainer.js @@ -31,7 +31,7 @@ function mapStateToProps(state) { data.datasets.push({ label: state.meters.byMeterID[meterID].name, id: state.meters.byMeterID[meterID].id, - timestamp: state.readings.bar.byMeterID[meterID][timeInterval][barDuration].start_timestamp, + timestamp: state.readings.bar.byMeterID[meterID][timeInterval][barDuration].timestamp, exportVals: state.readings.bar.byMeterID[meterID][timeInterval][barDuration].readings.map(arr => ({ x: arr[0], y: arr[1] })) }); } From c91b9a63a26c3ddb154c006d3b32b464e686507c Mon Sep 17 00:00:00 2001 From: Brandon Brown Date: Tue, 25 Apr 2017 19:35:43 -0500 Subject: [PATCH 16/20] the name of the exported file now dynamically changes based on the timestamp of the first exported reading. --- src/client/app/components/ExportComponent.jsx | 6 +++++- src/client/app/services/exportData.js | 9 ++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/client/app/components/ExportComponent.jsx b/src/client/app/components/ExportComponent.jsx index edeb633d9..3ec2ec618 100644 --- a/src/client/app/components/ExportComponent.jsx +++ b/src/client/app/components/ExportComponent.jsx @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import React from 'react'; +import moment from 'moment'; import graphExport from '../services/exportData'; export default class ExportComponent extends React.Component { @@ -21,7 +22,10 @@ export default class ExportComponent extends React.Component { */ exportReading() { const compressedData = this.props.exportVals.datasets; - graphExport(compressedData); + let time = compressedData[0].exportVals[0].x; + time = moment(time).format('ddddMMMDDYYYY'); + const name = `oedExport${time}.csv`; + graphExport(compressedData, name); } render() { diff --git a/src/client/app/services/exportData.js b/src/client/app/services/exportData.js index 27be312df..e23ce06ee 100644 --- a/src/client/app/services/exportData.js +++ b/src/client/app/services/exportData.js @@ -4,6 +4,8 @@ import moment from 'moment'; + + /** * Function to converts the compressed meter data into a CSV formatted string. * @param items The compressed meter data. @@ -18,7 +20,7 @@ function convertToCSV(items) { data.forEach(reading => { const info = reading.y; const timeStamp = moment(reading.x).format('dddd MMM DD YYYY hh:mm a'); - csvOutput += `${label},${info} kwh, ${timeStamp}\n`; + csvOutput += `${label},${info} kwh, ${timeStamp}\n`; // this assumes that meter readings are in kwh }); }); return csvOutput; @@ -39,11 +41,12 @@ function downloadCSV(inputCSV, fileName) { document.body.removeChild(element); } + /** * Function to export compressed data from the graph currently displaying. May be used for routing if more export options are added * @param dataSets An Object. The compressed data from each meter currently selected in the graph. */ -export default function graphExport(dataSets) { +export default function graphExport(dataSets, name) { const dataToExport = convertToCSV(dataSets); - downloadCSV(dataToExport, 'test.csv'); + downloadCSV(dataToExport, name); } From 16f4e3aafab7f414f347d916233422c801026372 Mon Sep 17 00:00:00 2001 From: Brandon Brown Date: Tue, 2 May 2017 19:40:46 -0500 Subject: [PATCH 17/20] Name of exported file includes its chartType, the export button is working with react-bootstrap however it is not formatted nicely compared to UIOptions. changed "line" and "bar" to chartType object. --- package.json | 1 + src/client/app/components/ExportComponent.jsx | 12 +++++++++--- src/client/app/containers/ExportContainer.js | 9 ++++++--- src/client/app/services/exportData.js | 9 +++++---- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 5215741df..78efafa3f 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "nodemailer": "~2.6.4", "pg-promise": "^5.3.5", "react": "^15.4.2", + "react-bootstrap": "^0.31.0", "react-chartjs-2": "^2.0.5", "react-dom": "^15.4.2", "react-rangeslider": "^2.0.1", diff --git a/src/client/app/components/ExportComponent.jsx b/src/client/app/components/ExportComponent.jsx index 3ec2ec618..d44807f25 100644 --- a/src/client/app/components/ExportComponent.jsx +++ b/src/client/app/components/ExportComponent.jsx @@ -3,13 +3,14 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import React from 'react'; +import { Button } from 'react-bootstrap'; import moment from 'moment'; import graphExport from '../services/exportData'; export default class ExportComponent extends React.Component { /** * Initializes the component's state, binds all functions to 'this' ExportComponent - * @param props The props passed down through the UIOptionsContainer + * @param props The props passed down through the ExportContainer */ constructor(props) { super(props); @@ -23,12 +24,17 @@ export default class ExportComponent extends React.Component { exportReading() { const compressedData = this.props.exportVals.datasets; let time = compressedData[0].exportVals[0].x; + const chart = compressedData[0].currentChart; time = moment(time).format('ddddMMMDDYYYY'); - const name = `oedExport${time}.csv`; + const name = `oedExport${time}${chart}.csv`; graphExport(compressedData, name); } render() { - return (); + return ( +
+ +
+ ); } } diff --git a/src/client/app/containers/ExportContainer.js b/src/client/app/containers/ExportContainer.js index 3f0d20d63..d73026c53 100644 --- a/src/client/app/containers/ExportContainer.js +++ b/src/client/app/containers/ExportContainer.js @@ -4,6 +4,7 @@ import { connect } from 'react-redux'; import ExportComponent from '../components/ExportComponent'; +import { chartTypes } from '../reducers/graph'; /** * @param {State} state @@ -17,21 +18,23 @@ function mapStateToProps(state) { const barDuration = state.graph.barDuration; for (const meterID of state.graph.selectedMeters) { - if (chart === 'line') { + if (chart === chartTypes.line) { readingsData = state.readings.line.byMeterID[meterID][timeInterval]; } else { readingsData = state.readings.bar.byMeterID[meterID][timeInterval][barDuration]; } - if (readingsData !== undefined && !readingsData.isFetching && chart === 'line') { + if (readingsData !== undefined && !readingsData.isFetching && chart === chartTypes.line) { data.datasets.push({ label: state.meters.byMeterID[meterID].name, id: state.meters.byMeterID[meterID].id, timestamp: state.readings.line.byMeterID[meterID][timeInterval].start_timestamp, + currentChart: chart, exportVals: state.readings.line.byMeterID[meterID][timeInterval].readings.map(arr => ({ x: arr[0], y: arr[1] })) }); - } else if (readingsData !== undefined && !readingsData.isFetching && chart === 'bar') { + } else if (readingsData !== undefined && !readingsData.isFetching && chart === chartTypes.bar) { data.datasets.push({ label: state.meters.byMeterID[meterID].name, id: state.meters.byMeterID[meterID].id, timestamp: state.readings.bar.byMeterID[meterID][timeInterval][barDuration].timestamp, + currentChart: chart, exportVals: state.readings.bar.byMeterID[meterID][timeInterval][barDuration].readings.map(arr => ({ x: arr[0], y: arr[1] })) }); } diff --git a/src/client/app/services/exportData.js b/src/client/app/services/exportData.js index e23ce06ee..6a0cba5b0 100644 --- a/src/client/app/services/exportData.js +++ b/src/client/app/services/exportData.js @@ -5,7 +5,6 @@ import moment from 'moment'; - /** * Function to converts the compressed meter data into a CSV formatted string. * @param items The compressed meter data. @@ -13,14 +12,14 @@ import moment from 'moment'; */ function convertToCSV(items) { - let csvOutput = 'Label,Readings,Timestamp\n'; + let csvOutput = 'Label,Readings,Start Timestamp\n'; items.forEach(set => { const data = set.exportVals; const label = set.label; data.forEach(reading => { const info = reading.y; - const timeStamp = moment(reading.x).format('dddd MMM DD YYYY hh:mm a'); - csvOutput += `${label},${info} kwh, ${timeStamp}\n`; // this assumes that meter readings are in kwh + const startTimeStamp = moment(reading.x).format('dddd MMM DD YYYY hh:mm a'); + csvOutput += `${label},${info} kwh, ${startTimeStamp}\n`; // this assumes that meter readings are in kwh }); }); return csvOutput; @@ -28,6 +27,7 @@ function convertToCSV(items) { /** * Function to download the formatted CSV file to the users computer. * @param inputCSV A String containing the formatted CSV data. + * @param fileName A string representing the name of the file. */ function downloadCSV(inputCSV, fileName) { const element = document.createElement('a'); @@ -45,6 +45,7 @@ function downloadCSV(inputCSV, fileName) { /** * Function to export compressed data from the graph currently displaying. May be used for routing if more export options are added * @param dataSets An Object. The compressed data from each meter currently selected in the graph. + * @param name the name of the file. */ export default function graphExport(dataSets, name) { const dataToExport = convertToCSV(dataSets); From 4c6b4ec056d9c00ef2a3e37f9e0ef6f6c0134177 Mon Sep 17 00:00:00 2001 From: Brandon Brown Date: Tue, 2 May 2017 20:18:13 -0500 Subject: [PATCH 18/20] updated a line of code to mitigate a likely bug that would appear if more chart types were added --- src/client/app/containers/ExportContainer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/app/containers/ExportContainer.js b/src/client/app/containers/ExportContainer.js index d73026c53..af5508a42 100644 --- a/src/client/app/containers/ExportContainer.js +++ b/src/client/app/containers/ExportContainer.js @@ -20,7 +20,7 @@ function mapStateToProps(state) { for (const meterID of state.graph.selectedMeters) { if (chart === chartTypes.line) { readingsData = state.readings.line.byMeterID[meterID][timeInterval]; - } else { readingsData = state.readings.bar.byMeterID[meterID][timeInterval][barDuration]; } + } else if (chart === chartTypes.bar) { readingsData = state.readings.bar.byMeterID[meterID][timeInterval][barDuration]; } if (readingsData !== undefined && !readingsData.isFetching && chart === chartTypes.line) { data.datasets.push({ label: state.meters.byMeterID[meterID].name, From a8ca7451d4dba40bd75fb97002f6ec6a953f4a17 Mon Sep 17 00:00:00 2001 From: Brandon Brown Date: Wed, 3 May 2017 10:46:28 -0500 Subject: [PATCH 19/20] Export component is now rendered as art of the UI options component fixing the formatting errors. --- src/client/app/components/HomeComponent.jsx | 2 -- src/client/app/components/UIOptionsComponent.jsx | 5 +++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/client/app/components/HomeComponent.jsx b/src/client/app/components/HomeComponent.jsx index 8a2de586f..2b67fe976 100644 --- a/src/client/app/components/HomeComponent.jsx +++ b/src/client/app/components/HomeComponent.jsx @@ -5,7 +5,6 @@ import React from 'react'; import HeaderComponent from './HeaderComponent'; import DashboardContainer from '../containers/DashboardContainer'; -import ExportContainer from '../containers/ExportContainer'; /** @@ -17,7 +16,6 @@ export default function HomeComponent() {
-
); } diff --git a/src/client/app/components/UIOptionsComponent.jsx b/src/client/app/components/UIOptionsComponent.jsx index f052f0306..cec9dce2c 100644 --- a/src/client/app/components/UIOptionsComponent.jsx +++ b/src/client/app/components/UIOptionsComponent.jsx @@ -7,6 +7,8 @@ import Slider from 'react-rangeslider'; import moment from 'moment'; import 'react-rangeslider/lib/index.css'; import { chartTypes } from '../reducers/graph'; +import ExportContainer from '../containers/ExportContainer'; + export default class UIOptionsComponent extends React.Component { /** @@ -104,6 +106,9 @@ export default class UIOptionsComponent extends React.Component {

Bar chart interval (days):

+
+ +
); From 394c15526d83ab2338d84d87898ba5ff651925d2 Mon Sep 17 00:00:00 2001 From: Brandon Brown Date: Wed, 3 May 2017 18:00:22 -0500 Subject: [PATCH 20/20] ExportComponent is now a functional component instead of a class --- src/client/app/components/ExportComponent.jsx | 33 +++++++------------ 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/src/client/app/components/ExportComponent.jsx b/src/client/app/components/ExportComponent.jsx index d44807f25..8029a6834 100644 --- a/src/client/app/components/ExportComponent.jsx +++ b/src/client/app/components/ExportComponent.jsx @@ -7,34 +7,23 @@ import { Button } from 'react-bootstrap'; import moment from 'moment'; import graphExport from '../services/exportData'; -export default class ExportComponent extends React.Component { - /** - * Initializes the component's state, binds all functions to 'this' ExportComponent - * @param props The props passed down through the ExportContainer - */ - constructor(props) { - super(props); - this.exportReading = this.exportReading.bind(this); - } - +const ExportComponent = props => { /** * Called when Export button is clicked. * Passes an object containing the selected meter data to a function for export. */ - exportReading() { - const compressedData = this.props.exportVals.datasets; + const exportReading = () => { + const compressedData = props.exportVals.datasets; let time = compressedData[0].exportVals[0].x; const chart = compressedData[0].currentChart; time = moment(time).format('ddddMMMDDYYYY'); const name = `oedExport${time}${chart}.csv`; graphExport(compressedData, name); - } - - render() { - return ( -
- -
- ); - } -} + }; + return ( +
+ +
+ ); +}; +export default ExportComponent;