New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Export group #83
Export group #83
Conversation
…le 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)
…le 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)
…ntainer to enable the export of the state object
# Conflicts: # src/client/app/components/LineChartComponent.jsx # src/client/app/containers/LineChartContainer.js
# Conflicts: # src/client/app/actions/readings.js # src/client/app/components/LineChartComponent.jsx # src/client/app/containers/LineChartContainer.js
# Conflicts: # src/client/app/actions/readings.js
…mpting the user for a name. linted
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not think reading.js or readMetasysData.js should have been changed. Am I missing something?
I linted the file and deleted a blank space, I also accidentally changed a comment in dashboardContainer which has now been fixed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, just a few small clean-up things
src/client/app/actions/exportData.js
Outdated
data.forEach(reading => { | ||
const info = reading.y; | ||
const timeStamp = moment(reading.x).format('dddd MMM DD YYYY hh:mm a'); | ||
if (isStart) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this conditional for?
src/client/app/actions/exportData.js
Outdated
const encodedUri = encodeURI(csvContent); | ||
const link = document.createElement('a'); | ||
let fileName = 'exportedDataOED'; | ||
fileName += '.csv'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can do as one line?
…nnecessary conditional.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me, thanks!
*/ | ||
|
||
function convertToCSV(items) { | ||
let csvOutput = 'id:,readings:,timestamp:\n'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should change timestamp
to start_timestamp
and end_timestamp
. You should also remove the colons in the labels.
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`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In addition to the meter ID, I would add the name of the meter. As meter IDs are internal to our system, someone would have no idea what the meter ID represented.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would even go so far as to think about not including the meterID
at all. meterID
is a surrogate key in our database and therefore doesn't mean anything outside the context of our system. There is no way it could help people consuming exported data. If it did it would be indicative of a bigger problem somewhere else in the export workflow.
const fileName = 'exportedDataOED.csv'; | ||
link.setAttribute('href', encodedUri); | ||
link.setAttribute('download', fileName); | ||
document.body.appendChild(link); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not an expert in this field, but is this the React
way to do things? Also, after clicking the link, it might be prudent to remove this link.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is definitely not the react way to do things. This throws an <a>
element onto the page outside of the react root div, then clicks it. This is definitely not the right way to do things.
I could see maybe making an <a>
element, clicking it, then removing it, but I'm sure there's a better way.
const csvContent = `data:text/csv;charset=utf-8,${inputCSV}`; | ||
const encodedUri = encodeURI(csvContent); | ||
const link = document.createElement('a'); | ||
const fileName = 'exportedDataOED.csv'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a more descriptive and "dynamic" name that we can give the file name? Perhaps include the start and end timestamps in the filename?
const timeInterval = state.graph.timeInterval; | ||
const data = { datasets: [] }; | ||
for (const meterID of state.graph.selectedMeters) { | ||
const readingsData = state.readings.byMeterID[meterID][timeInterval]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As of right now, this is completely correct. However, when the bar chart is added, readings will be retrieved from state.[line OR bar].readings...
Will the intention be to only grab line chart readings, bar chart readings, or both? If both, some additional logic will eventually be required here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your overall logic looks good and you're interacting correctly with redux. My main concern is definitely UIOptionsComponent
being a god object, and also the way that you make and click a link to make the user download a file. Good work overall. Just a few changes and this will be ready for master.
* Called when Export button is clicked. | ||
* Passes an object containing the selected meter data to a function for export. | ||
*/ | ||
exportReading() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm really not a fan of putting this logic in UIOptionsComponent
. It's a discrete piece of functionality that only depends on redux state, so I think you should extract it to its own component and include that in UIOptionsComponent
. UIOptionsComponent
is really getting to be a god object that violates single responsibility principle and we should try to fight that hard. I think the entire thing should be refactored, but that's a matter for a different pull request.
@@ -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: [] }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a fan of the ambiguous naming here. It's hard to figure out what part of UIOptionComponent
's functionality this pertains to. Refactoring this into its own container/component should mostly fix this issue, but some more detailed naming (and a comment or two) might still be a good idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
data
seems to just be a wrapper around datasets
. Why does it exist at all? Why not just use the datasets
list as your variable?
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`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would even go so far as to think about not including the meterID
at all. meterID
is a surrogate key in our database and therefore doesn't mean anything outside the context of our system. There is no way it could help people consuming exported data. If it did it would be indicative of a bigger problem somewhere else in the export workflow.
const fileName = 'exportedDataOED.csv'; | ||
link.setAttribute('href', encodedUri); | ||
link.setAttribute('download', fileName); | ||
document.body.appendChild(link); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is definitely not the react way to do things. This throws an <a>
element onto the page outside of the react root div, then clicks it. This is definitely not the right way to do things.
I could see maybe making an <a>
element, clicking it, then removing it, but I'm sure there's a better way.
@@ -10,23 +10,23 @@ const Meter = require('./../models/Meter'); | |||
async function readMetasysData(filePath) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for cleaning this up a bit while you were doing other things. It slipped through linting somehow. I see there are a few other linting errors in here, but they aren't your code so it's no big deal.
|
||
function convertToCSV(items) { | ||
let csvOutput = 'id:,readings:,timestamp:\n'; | ||
items.forEach(set => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You use a for ... of
loop in UIOptionsContainer.js
, but .forEach
here. Why the inconsistency?
# Conflicts: # src/client/app/actions/readings.js # src/client/app/components/UIOptionsComponent.jsx # src/client/app/containers/UIOptionsContainer.js # src/server/services/readMetasysData.js
…rently 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.
…a set export from linechart) timestamps for bar chart exports don't currently display correctly
…a set export from linechart) timestamps for bar chart exports don't currently display correctly
…mestamp of the first exported reading.
The export component now works with bar charts and the file name now changes based on the timestamp of the first reading. It still only exports each readings start time but as far as I can tell the charts only ever receive the start time of each reading. Maybe I am looking in the wrong place? I have also updated the actual save function to be a bit better but it essentially does the same thing as before. |
@kooolbreez1 You can get the current bar duration from |
that would work for the bar chart readings but not the line chart readings |
@kooolbreez1 Oh! Yeah, currently the only way to figure that out is to subtract the start of the second reading from the first one. They'll all have the same duration. We should probably start sending end timestamps to the client to do this more nicely. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost good, just a couple of minor things.
} | ||
|
||
render() { | ||
return (<button onClick={this.exportReading}>Export!</button>); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could use change this to a Bootstrap button? Preferably btn-default? Ideally, add the React-Bootstrap module and use it through that.
const barDuration = state.graph.barDuration; | ||
|
||
for (const meterID of state.graph.selectedMeters) { | ||
if (chart === 'line') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In reducers/graph.js, there is chartTypes
. Even though it doesn't really do anything, you should use them instead of the string 'line' and 'bar' so that we don't get confused when we start adding more types of charts.
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') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above comment.
const compressedData = this.props.exportVals.datasets; | ||
let time = compressedData[0].exportVals[0].x; | ||
time = moment(time).format('ddddMMMDDYYYY'); | ||
const name = `oedExport${time}.csv`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add in whether it is bar or line data in the title? Also, if possibly, could you add the end time of the interval. So the whole thing could look like: oedExportLineFeb272017-March032017.csv
…rking with react-bootstrap however it is not formatted nicely compared to UIOptions. changed "line" and "bar" to chartType object.
I believe I have made the changes that you requested @johndiiorio the only thing is that the export button doesn't display in line with the UIoptions panel, rather it appears in the bottom left corner of the screen |
…more chart types were added
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just one minor change!! Almost done.
@@ -15,6 +17,7 @@ export default function HomeComponent() { | |||
<div> | |||
<HeaderComponent renderLoginButton /> | |||
<DashboardContainer /> | |||
<ExportContainer /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you move this to be in UIOptionsComponent (at the bottom) instead of here?
…ixing the formatting errors.
@johndiiorio All fixed, it is now formatted nicely. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks almost perfect, and really my only concern is the functional component thing.
import moment from 'moment'; | ||
import graphExport from '../services/exportData'; | ||
|
||
export default class ExportComponent extends React.Component { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could easily be a functional component. No need for a class.
I believe I have made the changes that @simonbtomlinson wanted. |
@kooolbreez1 I think so too. I'll take a look when I get a chance, tomorrow at the latest. Thanks! |
This adds a button to the UI that when clicked exports the compressed data from the displayed graph for the currently selected meters as a CSV file.