Skip to content
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

Merged
merged 29 commits into from Sep 13, 2017
Merged

Export group #83

merged 29 commits into from Sep 13, 2017

Conversation

kooolbreez1
Copy link
Contributor

@kooolbreez1 kooolbreez1 commented Apr 13, 2017

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.

kooolbreez1 and others added 16 commits March 28, 2017 16:34
…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
Copy link

@sandeepacharya464 sandeepacharya464 left a 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?

@kooolbreez1
Copy link
Contributor Author

I linted the file and deleted a blank space, I also accidentally changed a comment in dashboardContainer which has now been fixed.

Copy link
Contributor

@RagingOcelot RagingOcelot left a 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

data.forEach(reading => {
const info = reading.y;
const timeStamp = moment(reading.x).format('dddd MMM DD YYYY hh:mm a');
if (isStart) {
Copy link
Contributor

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?

const encodedUri = encodeURI(csvContent);
const link = document.createElement('a');
let fileName = 'exportedDataOED';
fileName += '.csv';
Copy link
Contributor

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?

Copy link
Contributor

@RagingOcelot RagingOcelot left a 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';
Copy link
Contributor

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`;
Copy link
Contributor

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.

Copy link
Contributor

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);
Copy link
Contributor

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.

Copy link
Contributor

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';
Copy link
Contributor

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];
Copy link
Contributor

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.

Copy link
Contributor

@simonbtomlinson simonbtomlinson left a 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() {
Copy link
Contributor

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: [] };
Copy link
Contributor

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.

Copy link
Contributor

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`;
Copy link
Contributor

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);
Copy link
Contributor

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) {
Copy link
Contributor

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 => {
Copy link
Contributor

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
@kooolbreez1
Copy link
Contributor Author

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.

@simonbtomlinson
Copy link
Contributor

simonbtomlinson commented Apr 28, 2017

@kooolbreez1 You can get the current bar duration from state.graph.barDuration in redux. Then just add it to start points to get end points.

@kooolbreez1
Copy link
Contributor Author

that would work for the bar chart readings but not the line chart readings

@simonbtomlinson
Copy link
Contributor

@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.

Copy link
Contributor

@johndiiorio johndiiorio left a 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>);
Copy link
Contributor

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') {
Copy link
Contributor

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') {
Copy link
Contributor

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`;
Copy link
Contributor

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.
@kooolbreez1
Copy link
Contributor Author

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

Copy link
Contributor

@johndiiorio johndiiorio left a 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 />
Copy link
Contributor

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?

@kooolbreez1
Copy link
Contributor Author

@johndiiorio All fixed, it is now formatted nicely.

Copy link
Contributor

@simonbtomlinson simonbtomlinson left a 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 {
Copy link
Contributor

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.

@kooolbreez1
Copy link
Contributor Author

I believe I have made the changes that @simonbtomlinson wanted.

@simonbtomlinson
Copy link
Contributor

@kooolbreez1 I think so too. I'll take a look when I get a chance, tomorrow at the latest.

Thanks!

@kooolbreez1 kooolbreez1 merged commit a3033a0 into master Sep 13, 2017
@kooolbreez1 kooolbreez1 deleted the exportGroup branch September 13, 2017 15:43
@NoraCodes NoraCodes mentioned this pull request Sep 13, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants