From 636a478f40193adae475a5c1d8e280adc21201e9 Mon Sep 17 00:00:00 2001 From: JUDIT GRESKOVITS Date: Tue, 24 Jan 2017 17:37:08 +0000 Subject: [PATCH] Restructure redux state enable duration changing for metrics --- frontend/locales/en-us.json | 6 +- .../src/containers/metrics/add-metrics.js | 16 +- frontend/src/containers/metrics/index.js | 18 +- .../src/containers/metrics/metric-charts.js | 40 +- frontend/src/containers/service/metrics.js | 20 +- frontend/src/mock-state.json | 625 ++++++++++++++---- frontend/src/prop-types.js | 7 +- frontend/src/state/actions.js | 1 + frontend/src/state/reducers/metrics.js | 20 +- frontend/src/state/reducers/services.js | 42 +- frontend/src/state/selectors.js | 44 +- ui/src/components/metric/graph.js | 54 +- 12 files changed, 671 insertions(+), 222 deletions(-) diff --git a/frontend/locales/en-us.json b/frontend/locales/en-us.json index ca52afb0..2ea611a0 100644 --- a/frontend/locales/en-us.json +++ b/frontend/locales/en-us.json @@ -28,15 +28,15 @@ "metric": { "settings-label": "Settings" }, - "cpu_agg_usage": { + "cpu-agg-usage": { "title": "Aggregated CPU usage", "description": "CPU usages accross all of the CPU cores." }, - "cpu_wait_time": { + "cpu-wait-time": { "title": "Memory resident set size", "description": "Process memory that is actually stored in the RAM." }, - "zfs_used": { + "zfs-used": { "title": "Apache HTTP requests", "description": "Number of website requests to apache if it is used." } diff --git a/frontend/src/containers/metrics/add-metrics.js b/frontend/src/containers/metrics/add-metrics.js index adc9728f..d7532ead 100644 --- a/frontend/src/containers/metrics/add-metrics.js +++ b/frontend/src/containers/metrics/add-metrics.js @@ -16,13 +16,13 @@ const { } = AddMetric; const AddMetrics = ({ + datasets, metricTypes, - metrics, onAddMetric }) => { const added = (metric) => - Boolean(metrics.filter((m) => m.id === metric).length); + Boolean(datasets.filter((dataset) => dataset.type.id === metric).length); const addButton = (metric) => ( @@ -35,17 +35,17 @@ const AddMetrics = ({ ); const metricList = metricTypes.map((metric) => ( - + - + - + - { added(metric) ? addedButton : addButton(metric) } + { added(metric.id) ? addedButton : addButton(metric.id) } )); @@ -57,8 +57,8 @@ const AddMetrics = ({ }; AddMetrics.propTypes = { - metricTypes: React.PropTypes.arrayOf(React.PropTypes.string), - metrics: React.PropTypes.arrayOf(PropTypes.metric).isRequired, + datasets: React.PropTypes.arrayOf(PropTypes.dataset), + metricTypes: React.PropTypes.arrayOf(PropTypes.metric), onAddMetric: React.PropTypes.func.isRequired, }; diff --git a/frontend/src/containers/metrics/index.js b/frontend/src/containers/metrics/index.js index a8aa9ab8..b30d62db 100644 --- a/frontend/src/containers/metrics/index.js +++ b/frontend/src/containers/metrics/index.js @@ -20,13 +20,16 @@ const { const Metrics = ({ addMetric, - metrics, + datasets, metricTypes, metricTypeUuid = '', + metricDurationChange, service, toggleMonitorView = () => null }) => { + const onMonitorsClick = (ev) => toggleMonitorView(metricTypeUuid); + const onRemoveMetric = (ev) => {}; return (
@@ -37,12 +40,14 @@ const Metrics = ({
@@ -51,9 +56,10 @@ const Metrics = ({ Metrics.propTypes = { addMetric: React.PropTypes.func.isRequired, + datasets: React.PropTypes.arrayOf(PropTypes.dataset), + metricDurationChange: React.PropTypes.func.isRequired, metricTypeUuid: React.PropTypes.string, - metricTypes: React.PropTypes.arrayOf(React.PropTypes.string), - metrics: React.PropTypes.arrayOf(PropTypes.metric), + metricTypes: React.PropTypes.arrayOf(PropTypes.metric), service: PropTypes.service, toggleMonitorView: React.PropTypes.func.isRequired }; diff --git a/frontend/src/containers/metrics/metric-charts.js b/frontend/src/containers/metrics/metric-charts.js index 9a203801..2f31164f 100644 --- a/frontend/src/containers/metrics/metric-charts.js +++ b/frontend/src/containers/metrics/metric-charts.js @@ -20,34 +20,41 @@ const { const MetricCharts = ({ datasets, - duration = 360, durations = [ 360, 720, 1440, 2880 ], - onDurationChange = () => {}, - onSettingsClick = () => {}, + onDurationChange, + onSettingsClick, onRemoveMetric = () => {} }) => { const optionList = durations.map(duration => ( - )); - const metricList = datasets.map((dataset) => { - // TODO - // - yMeasurement '%' or not - // - yMin & yMax should all come from the metric type description + const metricList = datasets.map((dataset, index) => { + + const { + data, + duration=durations[0], + type + } = dataset; + + const onSelectChange = (evt) => + onDurationChange(Number(evt.target.value), dataset.uuid); return ( - + - {dataset.uuid} - + + + + {optionList} @@ -56,11 +63,11 @@ const MetricCharts = ({ ); @@ -74,8 +81,7 @@ const MetricCharts = ({ }; MetricCharts.propTypes = { - datasets: React.PropTypes.arrayOf(PropTypes.Dataset), - duration: React.PropTypes.number, + datasets: React.PropTypes.arrayOf(PropTypes.dataset), durations: React.PropTypes.arrayOf(React.PropTypes.number), onDurationChange: React.PropTypes.func.isRequired, onRemoveMetric: React.PropTypes.func.isRequired, diff --git a/frontend/src/containers/service/metrics.js b/frontend/src/containers/service/metrics.js index b3d86def..e13e9c4b 100644 --- a/frontend/src/containers/service/metrics.js +++ b/frontend/src/containers/service/metrics.js @@ -10,33 +10,41 @@ const { const { metricsByServiceIdSelector, + metricTypesSelector, serviceByIdSelector } = selectors; const { - addMetric + addMetric, + metricDurationChange } = actions; const mapStateToProps = (state, { params = {} }) => ({ - metrics: metricsByServiceIdSelector(params.serviceId)(state), - metricTypes: state.metrics.ui.types, + datasets: metricsByServiceIdSelector(params.serviceId)(state), + metricTypes: metricTypesSelector(state), service: serviceByIdSelector(params.serviceId)(state) }); const mapDispatchToProps = (dispatch) => ({ addMetric: (service) => (metric) => dispatch(addMetric({ - id: metric, + metric: metric, service: service.uuid - })) + })), + metricDurationChange: (service) => + (duration, dataset) => dispatch(metricDurationChange({ + duration, + dataset + })) }); const mergeProps = (stateProps, dispatchProps, ownProps) => ({ ...stateProps, ...dispatchProps, ...ownProps, - addMetric: dispatchProps.addMetric(stateProps.service) + addMetric: dispatchProps.addMetric(stateProps.service), + metricDurationChange: dispatchProps.metricDurationChange(stateProps.service) }); module.exports = connect( diff --git a/frontend/src/mock-state.json b/frontend/src/mock-state.json index 68359590..56b6e574 100644 --- a/frontend/src/mock-state.json +++ b/frontend/src/mock-state.json @@ -53,44 +53,112 @@ }, "metrics": { "ui": { - "types": [ - "cpu_agg_usage", - "cpu_wait_time", - "zfs_used", - "zfs_available", - "load_average", - "mem_agg_usage", - "mem_limit", - "mem_swap", - "mem_swap_limit", - "net_agg_packets_in", - "net_agg_packets_out", - "net_agg_bytes_in", - "net_agg_bytes_out", - "time_of_day" - ], "durations": [ - 360, - 720, - 1440, - 2880 + "360", + "720", + "1440", + "2880" ] }, "data": { "types": [{ - "uuid": "dca08514-72e5-46ce-ad91-e68b3b0914d4", - "name": "Aggregated CPU usage", - "id": "agg-cpu-usage" - }, { - "uuid": "9e77b50e-42d7-425d-8daf-c0e98e2bdd6a", - "id": "mem-res-set-size" - }, { - "uuid": "347dbdc7-15e3-4e12-8dfb-865d38526e14", - "id": "apache-http-reqs" - }, { "uuid": "2aaa237d-42b3-442f-9094-a17aa470014b", - "name": "Memory", - "id": "memory" + "name": "Aggregated CPU usage", + "id": "cpu-agg-usage", + "min": 0, + "max": 100, + "measurement": "%" + }, { + "uuid": "dca08514-72e5-46ce-ad91-e68b3b0914d6", + "name": "Aggregated CPU usage", + "id": "cpu-wait-time", + "min": 0, + "max": 100, + "measurement": "%" + }, { + "uuid": "dca08514-72e5-46ce-ad91-e68b3b0914d7", + "name": "Aggregated CPU usage", + "id": "zfs-used", + "min": 1, + "max": 2, + "measurement": "kb" + }, { + "uuid": "dca08514-72e5-46ce-ad91-e68b3b0914d8", + "name": "Aggregated CPU usage", + "id": "zfs-available", + "min": 0, + "max": 100, + "measurement": "%" + }, { + "uuid": "dca08514-72e5-46ce-ad91-e68b3b0914d9", + "name": "Aggregated CPU usage", + "id": "load-average", + "min": 0, + "max": 20, + "measurement": "kb" + }, { + "uuid": "dca08514-72e5-46ce-ad92-e68b3b0914d4", + "name": "Aggregated CPU usage", + "id": "mem-agg-usage", + "min": 0, + "max": 100, + "measurement": "%" + }, { + "uuid": "dca08514-72e5-46ce-ad93-e68b3b0914d4", + "name": "Aggregated CPU usage", + "id": "mem-limit", + "min": 0, + "max": 100, + "measurement": "%" + }, { + "uuid": "dca08514-72e5-46ce-ad94-e68b3b0914d4", + "name": "Aggregated CPU usage", + "id": "mem-swap", + "min": 0, + "max": 100, + "measurement": "%" + }, { + "uuid": "dca08514-72e5-46ce-ad95-e68b3b0914d4", + "name": "Aggregated CPU usage", + "id": "mem-swap-limit", + "min": 0, + "max": 100, + "measurement": "%" + }, { + "uuid": "dca08514-72e5-46ce-ad96-e68b3b0914d4", + "name": "Aggregated CPU usage", + "id": "net-agg-packets-in", + "min": 0, + "max": 100, + "measurement": "%" + }, { + "uuid": "dca08514-72e5-46ce-ad97-e68b3b0914d4", + "name": "Aggregated CPU usage", + "id": "net-agg-packets-out", + "min": 0, + "max": 100, + "measurement": "%" + }, { + "uuid": "dca08514-72e5-47ce-ad91-e68b3b0914d4", + "name": "Aggregated CPU usage", + "id": "net-agg-bytes-in", + "min": 0, + "max": 100, + "measurement": "%" + }, { + "uuid": "dca08514-72e5-48ce-ad91-e68b3b0914d4", + "name": "Aggregated CPU usage", + "id": "net-agg-bytes-out", + "min": 0, + "max": 100, + "measurement": "%" + }, { + "uuid": "dca08514-72e5-49ce-ad91-e68b3b0914d4", + "name": "Aggregated CPU usage", + "id": "time-of-day", + "min": 0, + "max": 100, + "measurement": "%" }], "datasets": [{ "type": "2aaa237d-42b3-442f-9094-a17aa470014b", @@ -197,7 +265,215 @@ "median": 30, "max": 49, "min": 30 + }, { + "firstQuartile": 15, + "thirdQuartile": 15, + "median": 15, + "max": 15, + "min": 15 + }, { + "firstQuartile": 26, + "thirdQuartile": 26, + "median": 26, + "max": 26, + "min": 26 + }, { + "firstQuartile": 17, + "thirdQuartile": 17, + "median": 17, + "max": 17, + "min": 17 + }, { + "firstQuartile": 15, + "thirdQuartile": 25, + "median": 19, + "max": 19, + "min": 20 + }, { + "firstQuartile": 19, + "thirdQuartile": 25, + "median": 21, + "max": 20, + "min": 25 + }, { + "firstQuartile": 24, + "thirdQuartile": 30, + "median": 25, + "max": 26, + "min": 10 + }, { + "firstQuartile": 28, + "thirdQuartile": 34, + "median": 30, + "max": 30, + "min": 30 + }, { + "firstQuartile": 30, + "thirdQuartile": 45, + "median": 35, + "max": 40, + "min": 40 + }, { + "firstQuartile": 20, + "thirdQuartile": 55, + "median": 45, + "max": 44, + "min": 44 + }, { + "firstQuartile": 55, + "thirdQuartile": 55, + "median": 55, + "max": 55, + "min": 55 + }, { + "firstQuartile": 57, + "thirdQuartile": 56, + "median": 57, + "max": 58, + "min": 57 + }, { + "firstQuartile": 57, + "thirdQuartile": 56, + "median": 56, + "max": 56, + "min": 56 + }, { + "firstQuartile": 60, + "thirdQuartile": 56, + "median": 60, + "max": 60, + "min": 60 + }, { + "firstQuartile": 57, + "thirdQuartile": 57, + "median": 57, + "max": 57, + "min": 57 + }, { + "firstQuartile": 57, + "thirdQuartile": 55, + "median": 55, + "max": 55, + "min": 55 + }, { + "firstQuartile": 20, + "thirdQuartile": 45, + "median": 45, + "max": 45, + "min": 45 + }, { + "firstQuartile": 15, + "thirdQuartile": 40, + "median": 30, + "max": 49, + "min": 30 + }, { + "firstQuartile": 15, + "thirdQuartile": 15, + "median": 15, + "max": 15, + "min": 15 + }, { + "firstQuartile": 26, + "thirdQuartile": 26, + "median": 26, + "max": 26, + "min": 26 + }, { + "firstQuartile": 17, + "thirdQuartile": 17, + "median": 17, + "max": 17, + "min": 17 + }, { + "firstQuartile": 15, + "thirdQuartile": 25, + "median": 19, + "max": 19, + "min": 20 + }, { + "firstQuartile": 19, + "thirdQuartile": 25, + "median": 21, + "max": 20, + "min": 25 + }, { + "firstQuartile": 24, + "thirdQuartile": 30, + "median": 25, + "max": 26, + "min": 27 + }, { + "firstQuartile": 28, + "thirdQuartile": 34, + "median": 30, + "max": 30, + "min": 30 + }, { + "firstQuartile": 30, + "thirdQuartile": 45, + "median": 35, + "max": 40, + "min": 40 + }, { + "firstQuartile": 20, + "thirdQuartile": 55, + "median": 45, + "max": 44, + "min": 44 + }, { + "firstQuartile": 55, + "thirdQuartile": 55, + "median": 55, + "max": 55, + "min": 55 + }, { + "firstQuartile": 57, + "thirdQuartile": 56, + "median": 57, + "max": 58, + "min": 57 + }, { + "firstQuartile": 57, + "thirdQuartile": 56, + "median": 56, + "max": 56, + "min": 56 + }, { + "firstQuartile": 60, + "thirdQuartile": 56, + "median": 60, + "max": 60, + "min": 60 + }, { + "firstQuartile": 57, + "thirdQuartile": 57, + "median": 57, + "max": 57, + "min": 57 + }, { + "firstQuartile": 57, + "thirdQuartile": 55, + "median": 55, + "max": 55, + "min": 55 + }, { + "firstQuartile": 20, + "thirdQuartile": 45, + "median": 45, + "max": 45, + "min": 45 + }, { + "firstQuartile": 15, + "thirdQuartile": 40, + "median": 30, + "max": 49, + "min": 30 }] + },{ + "type": "dca08514-72e5-46ce-ad91-e68b3b0914d9", + "uuid": "4e6ee79a-7453-4fc6-b9da-7ae1e41138ed", + "data": [{"firstQuartile":1.62,"thirdQuartile":1.62,"median":1.62,"max":1.62,"min":1.62},{"firstQuartile":1.67,"thirdQuartile":1.67,"median":1.67,"max":1.67,"min":1.67},{"firstQuartile":1.63,"thirdQuartile":1.63,"median":1.63,"max":1.63,"min":1.63},{"firstQuartile":1.62,"thirdQuartile":1.66,"median":1.64,"max":1.64,"min":1.64},{"firstQuartile":1.64,"thirdQuartile":1.66,"median":1.64,"max":1.64,"min":1.66},{"firstQuartile":1.66,"thirdQuartile":1.69,"median":1.66,"max":1.67,"min":1.67},{"firstQuartile":1.68,"thirdQuartile":1.70,"median":1.69,"max":1.69,"min":1.69},{"firstQuartile":1.69,"thirdQuartile":1.75,"median":1.71,"max":1.73,"min":1.73},{"firstQuartile":1.64,"thirdQuartile":1.80,"median":1.75,"max":1.75,"min":1.75},{"firstQuartile":1.80,"thirdQuartile":1.80,"median":1.80,"max":1.80,"min":1.80},{"firstQuartile":1.81,"thirdQuartile":1.80,"median":1.81,"max":1.81,"min":1.81},{"firstQuartile":1.81,"thirdQuartile":1.80,"median":1.80,"max":1.80,"min":1.80},{"firstQuartile":1.82,"thirdQuartile":1.80,"median":1.82,"max":1.82,"min":1.82},{"firstQuartile":1.81,"thirdQuartile":1.81,"median":1.81,"max":1.81,"min":1.81},{"firstQuartile":1.81,"thirdQuartile":1.80,"median":1.80,"max":1.80,"min":1.80},{"firstQuartile":1.64,"thirdQuartile":1.75,"median":1.75,"max":1.75,"min":1.75},{"firstQuartile":1.62,"thirdQuartile":1.73,"median":1.69,"max":1.77,"min":1.69},{"firstQuartile":1.62,"thirdQuartile":1.62,"median":1.62,"max":1.62,"min":1.62},{"firstQuartile":1.67,"thirdQuartile":1.67,"median":1.67,"max":1.67,"min":1.67},{"firstQuartile":1.63,"thirdQuartile":1.63,"median":1.63,"max":1.63,"min":1.63},{"firstQuartile":1.62,"thirdQuartile":1.66,"median":1.64,"max":1.64,"min":1.64},{"firstQuartile":1.64,"thirdQuartile":1.66,"median":1.64,"max":1.64,"min":1.66},{"firstQuartile":1.66,"thirdQuartile":1.69,"median":1.66,"max":1.67,"min":1.59},{"firstQuartile":1.68,"thirdQuartile":1.70,"median":1.69,"max":1.69,"min":1.69},{"firstQuartile":1.69,"thirdQuartile":1.75,"median":1.71,"max":1.73,"min":1.73},{"firstQuartile":1.64,"thirdQuartile":1.80,"median":1.75,"max":1.75,"min":1.75},{"firstQuartile":1.80,"thirdQuartile":1.80,"median":1.80,"max":1.80,"min":1.80},{"firstQuartile":1.81,"thirdQuartile":1.80,"median":1.81,"max":1.81,"min":1.81},{"firstQuartile":1.81,"thirdQuartile":1.80,"median":1.80,"max":1.80,"min":1.80},{"firstQuartile":1.82,"thirdQuartile":1.80,"median":1.82,"max":1.82,"min":1.82},{"firstQuartile":1.81,"thirdQuartile":1.81,"median":1.81,"max":1.81,"min":1.81},{"firstQuartile":1.81,"thirdQuartile":1.80,"median":1.80,"max":1.80,"min":1.80},{"firstQuartile":1.64,"thirdQuartile":1.75,"median":1.75,"max":1.75,"min":1.75},{"firstQuartile":1.62,"thirdQuartile":1.73,"median":1.69,"max":1.77,"min":1.69},{"firstQuartile":1.62,"thirdQuartile":1.62,"median":1.62,"max":1.62,"min":1.62},{"firstQuartile":1.67,"thirdQuartile":1.67,"median":1.67,"max":1.67,"min":1.67},{"firstQuartile":1.63,"thirdQuartile":1.63,"median":1.63,"max":1.63,"min":1.63},{"firstQuartile":1.62,"thirdQuartile":1.66,"median":1.64,"max":1.64,"min":1.64},{"firstQuartile":1.64,"thirdQuartile":1.66,"median":1.64,"max":1.64,"min":1.66},{"firstQuartile":1.66,"thirdQuartile":1.69,"median":1.66,"max":1.67,"min":1.67},{"firstQuartile":1.68,"thirdQuartile":1.70,"median":1.69,"max":1.69,"min":1.69},{"firstQuartile":1.69,"thirdQuartile":1.75,"median":1.71,"max":1.73,"min":1.73},{"firstQuartile":1.64,"thirdQuartile":1.80,"median":1.75,"max":1.75,"min":1.75},{"firstQuartile":1.80,"thirdQuartile":1.80,"median":1.80,"max":1.80,"min":1.80},{"firstQuartile":1.81,"thirdQuartile":1.80,"median":1.81,"max":1.81,"min":1.81},{"firstQuartile":1.81,"thirdQuartile":1.80,"median":1.80,"max":1.80,"min":1.80},{"firstQuartile":1.82,"thirdQuartile":1.80,"median":1.82,"max":1.82,"min":1.82},{"firstQuartile":1.81,"thirdQuartile":1.81,"median":1.81,"max":1.81,"min":1.81},{"firstQuartile":1.81,"thirdQuartile":1.80,"median":1.80,"max":1.80,"min":1.80},{"firstQuartile":1.64,"thirdQuartile":1.75,"median":1.75,"max":1.75,"min":1.75},{"firstQuartile":1.62,"thirdQuartile":1.73,"median":1.69,"max":1.77,"min":1.69}] }] } }, @@ -320,55 +596,80 @@ "name": "Nginx", "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", "instances": 1, - "metrics": [ - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" - ] + "metrics": [{ + "type": "cpu-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }, { + "type": "zfs-used", + "dataset": "4e6ee79a-7453-4fc6-b9da-7ae1e41138ed" + }, { + "type": "mem-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }] }, { "uuid": "be227788-74f1-4e5b-a85f-b5c71cbae8d8", "id": "wordpress", "name": "Wordpress", "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", "instances": 1, - "metrics": [ - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" - ] + "metrics": [{ + "type": "cpu-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }, { + "type": "zfs-used", + "dataset": "4e6ee79a-7453-4fc6-b9da-7ae1e41138ed" + }, { + "type": "mem-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }] }, { "uuid": "6a0eee76-c019-413b-9d5f-44712b55b993", "id": "nfs", "name": "NFS", "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", "instances": 1, - "metrics": [ - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" - ] + "metrics": [{ + "type": "cpu-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }, { + "type": "zfs-used", + "dataset": "4e6ee79a-7453-4fc6-b9da-7ae1e41138ed" + }, { + "type": "mem-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }] }, { "uuid": "6d31aff4-de1e-4042-a983-fbd23d5c530c", "id": "memcached", "name": "Memcached", "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", "instances": 5, - "metrics": [ - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" - ] + "metrics": [{ + "type": "cpu-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }, { + "type": "zfs-used", + "dataset": "4e6ee79a-7453-4fc6-b9da-7ae1e41138ed" + }, { + "type": "mem-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }] }, { "uuid": "4ee4103e-1a52-4099-a48e-01588f597c70", "id": "percona", "name": "Percona", "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", "instances": 5, - "metrics": [ - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" - ] + "metrics": [{ + "type": "cpu-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }, { + "type": "zfs-used", + "dataset": "4e6ee79a-7453-4fc6-b9da-7ae1e41138ed" + }, { + "type": "mem-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }] }, { "uuid": "9572d367-c4ae-4fb1-8ad5-f5e3830e7034", "id": "primary", @@ -376,11 +677,16 @@ "parent": "4ee4103e-1a52-4099-a48e-01588f597c70", "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", "instances": 1, - "metrics": [ - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" - ] + "metrics": [{ + "type": "cpu-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }, { + "type": "zfs-used", + "dataset": "4e6ee79a-7453-4fc6-b9da-7ae1e41138ed" + }, { + "type": "mem-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }] }, { "uuid": "c8411ef0-ab39-42cb-a704-d20b170eff31", "id": "secondaries", @@ -388,22 +694,32 @@ "parent": "4ee4103e-1a52-4099-a48e-01588f597c70", "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", "instances": 4, - "metrics": [ - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" - ] + "metrics": [{ + "type": "cpu-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }, { + "type": "zfs-used", + "dataset": "4e6ee79a-7453-4fc6-b9da-7ae1e41138ed" + }, { + "type": "mem-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }] }, { "uuid": "97c68055-db88-45c9-ad49-f26da4264777", "id": "consul", "name": "Consul", "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", "instances": 1, - "metrics": [ - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" - ] + "metrics": [{ + "type": "cpu-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }, { + "type": "zfs-used", + "dataset": "4e6ee79a-7453-4fc6-b9da-7ae1e41138ed" + }, { + "type": "mem-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }] }] }, "instances": { @@ -416,121 +732,176 @@ "datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb", "service": "be227788-74f1-4e5b-a85f-b5c71cbae8d8", "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", - "metrics": [ - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" - ] + "metrics": [{ + "type": "cpu-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }, { + "type": "zfs-used", + "dataset": "4e6ee79a-7453-4fc6-b9da-7ae1e41138ed" + }, { + "type": "mem-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }] }, { "uuid": "0db6db53-de6f-4378-839e-5d5b452fbaf2", "name": "nfs_01", "datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb", "service": "6a0eee76-c019-413b-9d5f-44712b55b993", "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", - "metrics": [ - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" - ] + "metrics": [{ + "type": "cpu-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }, { + "type": "zfs-used", + "dataset": "4e6ee79a-7453-4fc6-b9da-7ae1e41138ed" + }, { + "type": "mem-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }] }, { "uuid": "250c8a6c-7d02-49a9-8abd-e1c22773041d", "name": "consul", "datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb", "service": "97c68055-db88-45c9-ad49-f26da4264777", "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", - "metrics": [ - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" - ] + "metrics": [{ + "type": "cpu-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }, { + "type": "zfs-used", + "dataset": "4e6ee79a-7453-4fc6-b9da-7ae1e41138ed" + }, { + "type": "mem-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }] }, { "uuid": "2c921f3a-8bc3-4f57-9cd7-789ebae72061", "name": "memcache_01", "datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb", "service": "6d31aff4-de1e-4042-a983-fbd23d5c530c", "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", - "metrics": [ - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" - ] + "metrics": [{ + "type": "cpu-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }, { + "type": "zfs-used", + "dataset": "4e6ee79a-7453-4fc6-b9da-7ae1e41138ed" + }, { + "type": "mem-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }] }, { "uuid": "68d3046e-8e34-4f5d-a0e5-db3795a250fd", "name": "memcache_02", "datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb", "service": "6d31aff4-de1e-4042-a983-fbd23d5c530c", "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", - "metrics": [ - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" - ] + "metrics": [{ + "type": "cpu-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }, { + "type": "zfs-used", + "dataset": "4e6ee79a-7453-4fc6-b9da-7ae1e41138ed" + }, { + "type": "mem-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }] }, { "uuid": "2ea99763-3b44-4179-8393-d66d94961051", "name": "memcache_03", "datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb", "service": "6d31aff4-de1e-4042-a983-fbd23d5c530c", "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", - "metrics": [ - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" - ] + "metrics": [{ + "type": "cpu-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }, { + "type": "zfs-used", + "dataset": "4e6ee79a-7453-4fc6-b9da-7ae1e41138ed" + }, { + "type": "mem-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }] }, { "uuid": "25f6bc62-63b8-4959-908e-1f6d7ff6341d", "name": "memcache_04", "datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb", "service": "6d31aff4-de1e-4042-a983-fbd23d5c530c", "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", - "metrics": [ - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" - ] + "metrics": [{ + "type": "cpu-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }, { + "type": "zfs-used", + "dataset": "4e6ee79a-7453-4fc6-b9da-7ae1e41138ed" + }, { + "type": "mem-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }] }, { "uuid": "8be01042-0281-4a77-a357-25979e87bf3d", "name": "memcache_05", "datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb", "service": "6d31aff4-de1e-4042-a983-fbd23d5c530c", "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", - "metrics": [ - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" - ] + "metrics": [{ + "type": "cpu-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }, { + "type": "zfs-used", + "dataset": "4e6ee79a-7453-4fc6-b9da-7ae1e41138ed" + }, { + "type": "mem-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }] }, { "uuid": "3d652e9d-73e8-4a6f-8171-84fa83740662", "name": "nginx", "datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb", "service": "081a792c-47e0-4439-924b-2efa9788ae9e", "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", - "metrics": [ - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" - ] + "metrics": [{ + "type": "cpu-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }, { + "type": "zfs-used", + "dataset": "4e6ee79a-7453-4fc6-b9da-7ae1e41138ed" + }, { + "type": "mem-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }] }, { "uuid": "c3ec7633-a02b-4615-86a0-9e6faeaae94b", "name": "percona-primary", "datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb", "service": "4ee4103e-1a52-4099-a48e-01588f597c70", "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", - "metrics": [ - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" - ] + "metrics": [{ + "type": "cpu-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }, { + "type": "zfs-used", + "dataset": "4e6ee79a-7453-4fc6-b9da-7ae1e41138ed" + }, { + "type": "mem-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }] }, { "uuid": "c2b5fec2-31e2-41a7-b7fc-cd0bb1822e76", "name": "percona-secundary", "datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb", "service": "4ee4103e-1a52-4099-a48e-01588f597c70", "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", - "metrics": [ - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec", - "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" - ] + "metrics": [{ + "type": "cpu-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }, { + "type": "zfs-used", + "dataset": "4e6ee79a-7453-4fc6-b9da-7ae1e41138ed" + }, { + "type": "mem-agg-usage", + "dataset": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec" + }] }] } } diff --git a/frontend/src/prop-types.js b/frontend/src/prop-types.js index f7baee89..6b3c9685 100644 --- a/frontend/src/prop-types.js +++ b/frontend/src/prop-types.js @@ -36,7 +36,10 @@ const Instance = React.PropTypes.shape({ }); const Metric = React.PropTypes.shape({ - ...BaseObject + ...BaseObject, + min: React.PropTypes.number, + max: React.PropTypes.number, + measurement: React.PropTypes.string }); const MetricType = React.PropTypes.shape({ @@ -45,7 +48,7 @@ const MetricType = React.PropTypes.shape({ const Dataset = React.PropTypes.shape({ uuid: React.PropTypes.string, - type: React.PropTypes.string, + type: MetricType, data: React.PropTypes.arrayOf( React.PropTypes.shape({ firstQuartile: React.PropTypes.number, diff --git a/frontend/src/state/actions.js b/frontend/src/state/actions.js index e24eb3c6..1cf6563e 100644 --- a/frontend/src/state/actions.js +++ b/frontend/src/state/actions.js @@ -13,6 +13,7 @@ module.exports = { toggleHeaderTooltip: createAction(`${APP}/TOGGLE_HEADER_TOOLTIP`), toggleServiceCollapsed: createAction(`${APP}/TOGGLE_SERVICE_COLLAPSED`), addMetric: createAction(`${APP}/ADD_METRIC`), + metricDurationChange: createAction(`${APP}/METRIC_DURATION_CHANGE`), toggleInstanceCollapsed: createAction(`${APP}/TOGGLE_INSTANCE_COLLAPSED`), toggleMonitorView: createAction(`${APP}/TOGGLE_MONITOR_VIEW`), switchMonitorViewPage: createAction(`${APP}/SWITCH_MONITOR_VIEW_PAGE`), diff --git a/frontend/src/state/reducers/metrics.js b/frontend/src/state/reducers/metrics.js index 52c27dff..f05ad7a4 100644 --- a/frontend/src/state/reducers/metrics.js +++ b/frontend/src/state/reducers/metrics.js @@ -7,23 +7,19 @@ const { } = ReduxActions; const { - addMetric + metricDurationChange } = actions; -// This will need to be handled by an async action -// to update on the server too module.exports = handleActions({ - [addMetric.toString()]: (state, action) => { + [metricDurationChange.toString()]: (state, action) => { return ({ ...state, - data: { - types: [ - ...state.data.types, - action.payload - ], - datasets: [ - ...state.data.datasets - ] + ui: { + ...state.ui, + [action.payload.dataset]: { + ...state.ui[action.payload.dataset], + duration: action.payload.duration + } } }); } diff --git a/frontend/src/state/reducers/services.js b/frontend/src/state/reducers/services.js index d67ffb59..940181b8 100644 --- a/frontend/src/state/reducers/services.js +++ b/frontend/src/state/reducers/services.js @@ -8,6 +8,7 @@ const { } = ReduxActions; const { + addMetric, toggleServiceCollapsed } = actions; @@ -15,6 +16,45 @@ const { toggleCollapsed } = common; +const getMetrics = (stateMetrics, addMetric, metric) => { + const metrics = stateMetrics.map((m) => { + return ({ + ...m + }); + }); + if(addMetric) { + metrics.push({ + type: metric + }); + } + return metrics; +}; + +const getServices = (stateServices, service, metric) => { + return stateServices.map((s) => { + return ({ + ...s, + metrics: getMetrics( + s.metrics, + s.uuid === service, + metric + ) + }); + }); +}; + module.exports = handleActions({ - [toggleServiceCollapsed.toString()]: toggleCollapsed + [toggleServiceCollapsed.toString()]: toggleCollapsed, + // This will need to be handled by an async action + // to update on the server too + [addMetric.toString()]: (state, action) => { + return ({ + ...state, + data: getServices( + state.data, + action.payload.service, + action.payload.metric + ) + }); + } }, {}); diff --git a/frontend/src/state/selectors.js b/frontend/src/state/selectors.js index ebfe86c8..ff6ee9d0 100644 --- a/frontend/src/state/selectors.js +++ b/frontend/src/state/selectors.js @@ -20,8 +20,10 @@ const collapsedServices = (state) => get(state, 'services.ui.collapsed', []); const collapsedInstances = (state) => get(state, 'instances.ui.collapsed', []); const instances = (state) => get(state, 'instances.data', []); const members = (state) => get(state, 'members.data', []); +const metricsUI = (state) => get(state, 'metrics.ui', {}); const metricTypes = (state) => get(state, 'metrics.data.types', []); -const metricDatasets = (state) => get(state, 'metrics.data.datasets', []); +// const metricDatasets = (state) => get(state, 'metrics.data.datasets', []); +const metricsData = (state) => get(state, 'metrics.data', {}); const projectById = (projectId) => createSelector( projects, @@ -52,14 +54,16 @@ const orgSections = (orgId) => createSelector( const isCollapsed = (collapsed, uuid) => collapsed.indexOf(uuid) >= 0; -const datasets = (metrics, uuids) => uuids.map((uuid) => find(metrics, [ - 'uuid', - uuid -])); +const datasets = (metricsData, serviceOrInstanceMetrics, metricsUI) => + serviceOrInstanceMetrics.map((soim) => ({ + ...find(metricsData.datasets, [ 'uuid', soim.dataset ]), + type: find(metricsData.types, [ 'id', soim.type ]), + ...metricsUI[soim.dataset] + })); const servicesByProjectId = (projectId) => createSelector( - [services, projectById(projectId), collapsedServices, metricDatasets], - (services, project, collapsed, metrics) => + [services, projectById(projectId), collapsedServices, metricsData, metricsUI], + (services, project, collapsed, metrics, metricsUI) => services.filter((s) => s.project === project.uuid) .map((service) => ({ ...service, @@ -68,33 +72,36 @@ const servicesByProjectId = (projectId) => createSelector( .filter((s) => !s.parent) .map((service) => ({ ...service, - metrics: datasets(metrics, service.metrics), + metrics: datasets(metrics, service.metrics, metricsUI), collapsed: isCollapsed(collapsed, service.uuid), services: service.services.map((service) => ({ ...service, - metrics: datasets(metrics, service.metrics), + metrics: datasets(metrics, service.metrics, metricsUI), collapsed: isCollapsed(collapsed, service.uuid) })) })) ); const instancesByServiceId = (serviceId) => createSelector( - [instances, serviceById(serviceId), collapsedInstances, metricDatasets], - (instances, service, collapsed, metrics) => + [ + instances, + serviceById(serviceId), + collapsedInstances, + metricsData, + metricsUI + ], + (instances, service, collapsed, metrics, metricsUI) => instances.filter((i) => i.service === service.uuid) .map((instance) => ({ ...instance, - metrics: datasets(metrics, instance.metrics), + metrics: datasets(metrics, instance.metrics, metricsUI), collapsed: isCollapsed(collapsed, instance.uuid) })) ); const metricsByServiceId = (serviceId) => createSelector( - [metricTypes, serviceById(serviceId), metricDatasets], - (metricTypes, service, metrics) => ({ - types: metricTypes.filter((i) => i.service === service.uuid), - datasets: datasets(metrics, service.metrics) - }) + [serviceById(serviceId), metricsData, metricsUI], + (service, metrics, metricsUI) => datasets(metrics, service.metrics, metricsUI) ); const metricTypeByUuid = (metricTypeUuid) => createSelector( @@ -104,7 +111,7 @@ const metricTypeByUuid = (metricTypeUuid) => createSelector( const instancesByProjectId = (projectId) => createSelector( - [instances, projectById(projectId), collapsedInstances, metricDatasets], + [instances, projectById(projectId), collapsedInstances, metricsData], (instances, project, collapsed, metrics) => instances.filter((i) => i.project === project.uuid) .map((instance) => ({ @@ -143,6 +150,7 @@ module.exports = { servicesByProjectIdSelector: servicesByProjectId, instancesByServiceIdSelector: instancesByServiceId, metricsByServiceIdSelector: metricsByServiceId, + metricTypesSelector: metricTypes, instancesByProjectIdSelector: instancesByProjectId, metricTypeByUuidSelector: metricTypeByUuid, peopleByOrgIdSelector: peopleByOrgId diff --git a/ui/src/components/metric/graph.js b/ui/src/components/metric/graph.js index cf581633..0be2cc5c 100644 --- a/ui/src/components/metric/graph.js +++ b/ui/src/components/metric/graph.js @@ -46,7 +46,7 @@ class Graph extends React.Component { }] }, options: { - animation: false, + animation: false, layout: { padding: 10 }, @@ -59,9 +59,9 @@ class Graph extends React.Component { unitStepSize: xUnitStepSize, max: xMax, min: xMin, - /*displayFormats: { + displayFormats: { hour: 'MMM D, hA' - }*/ + } }, }], yAxes: [{ @@ -70,7 +70,9 @@ class Graph extends React.Component { min: yMin, max: yMax, callback: (value, index, values) => { - return `${value.toFixed(2)}${yMeasurement}`; + return value < 10 && value !== 0 ? + `${value.toFixed(2)}${yMeasurement}` : + `${value}${yMeasurement}`; } } }] @@ -100,11 +102,15 @@ class Graph extends React.Component { this._chart.update(0); } + shouldComponentUpdate(nextProps, nextState) { + return false; + } + processData(props) { const { data = [], duration = 360 - } = this.props; + } = props; // I'm going to assume that data will be structured in 10min intervals... // And that newest data will be at the end... // Let's rock and roll! @@ -113,21 +119,24 @@ class Graph extends React.Component { // first time on scale x const before = moment().subtract(duration, 'minutes'); // remove leading data before first time on scale x - const totalData = data.slice(data.length - 1 - duration/10); - // adjust time of first data, if there's less data than would fill the chart - const start = moment(before) - .add(duration - (totalData.length-1)*10, 'minutes'); - // add times to data - const dataWithTime = totalData.map((d, i) => { - const add = i*10; - return Object.assign( - {}, - d, - { - x: moment(start).add(add, 'minutes').toDate() - } - ); - }); + let dataWithTime = []; + if(data && data.length) { + const totalData = data.slice(data.length - 1 - duration/10); + // adjust time of first data, if there's less data than would fill the chart + const start = moment(before) + .add(duration - (totalData.length-1)*10, 'minutes'); + // add times to data + dataWithTime = totalData.map((d, i) => { + const add = i*10; + return Object.assign( + {}, + d, + { + x: moment(start).add(add, 'minutes').toDate() + } + ); + }); + } // set min and max const xMax = now.toDate(); @@ -152,6 +161,7 @@ class Graph extends React.Component { } render() { + return (