From 2c7d7512045bfca28e146ae2ac1384e98c4946bb Mon Sep 17 00:00:00 2001 From: JUDIT GRESKOVITS Date: Wed, 18 Jan 2017 17:52:20 +0000 Subject: [PATCH] Add functional metric chart with customisable time scale --- ui/package.json | 1 + ui/src/components/metric/body.js | 33 --- ui/src/components/metric/graph.js | 174 +++++++++++ ui/src/components/metric/icon-settings.svg | 18 ++ ui/src/components/metric/index.js | 2 +- ui/src/components/metric/metric-data.js | 307 ++++++++++++++++++++ ui/src/components/metric/select.js | 10 +- ui/src/components/metric/settings-button.js | 3 +- ui/src/components/metric/story.js | 81 +++++- ui/src/components/metric/view.js | 6 + ui/yarn.lock | 2 +- 11 files changed, 591 insertions(+), 46 deletions(-) delete mode 100644 ui/src/components/metric/body.js create mode 100644 ui/src/components/metric/graph.js create mode 100644 ui/src/components/metric/icon-settings.svg create mode 100644 ui/src/components/metric/metric-data.js diff --git a/ui/package.json b/ui/package.json index 377fe482..5e14c5d4 100644 --- a/ui/package.json +++ b/ui/package.json @@ -24,6 +24,7 @@ "lodash.isfunction": "^3.0.8", "lodash.isstring": "^4.0.1", "lodash.isundefined": "^3.0.1", + "moment": "^2.17.1", "param-case": "^2.1.0", "random-natural": "^1.0.3", "react": "^15.4.1", diff --git a/ui/src/components/metric/body.js b/ui/src/components/metric/body.js deleted file mode 100644 index 65c7effc..00000000 --- a/ui/src/components/metric/body.js +++ /dev/null @@ -1,33 +0,0 @@ -const React = require('react'); -const Styled = require('styled-components'); -const fns = require('../../shared/functions'); - -const { - remcalc -} = fns; - -const { - default: styled -} = Styled; - -const StyledBody = styled.div` - margin: 0; - width: 100%; - height: ${remcalc(264)}; -`; - -const Body = ({ - children -}) => { - return ( - - {children} - - ); -}; - -Body.propTypes = { - children: React.PropTypes.node -}; - -module.exports = Body; diff --git a/ui/src/components/metric/graph.js b/ui/src/components/metric/graph.js new file mode 100644 index 00000000..39d6d4da --- /dev/null +++ b/ui/src/components/metric/graph.js @@ -0,0 +1,174 @@ +const React = require('react'); +const Styled = require('styled-components'); +const moment = require('moment'); +const Chart = require('chart.js'); +const whisker = require('chartjs-chart-box-plot'); + +whisker(Chart); + +const { + default: styled +} = Styled; + +const Container = styled.div` + position: relative; + height: 100%; + width: 100%; +`; + +const Canvas = styled.canvas` + +`; + +class Graph extends React.Component { + + componentDidMount() { + const { + yMax = 100, + yMin = 0, + yMeasurement = '%' + } = this.props; + + const { + data, + xMax, + xMin, + xUnitStepSize + } = this.processData(this.props); + + this._chart = new Chart(this._refs.component, { + type: 'whisker', + responsive: true, + maintainAspectRatio: true, + data: { + datasets: [{ + data: data + }] + }, + options: { + layout: { + padding: 10 + }, + scales: { + xAxes: [{ + display: true, + type: 'time', + time: { + unit: 'minute', + unitStepSize: xUnitStepSize, + max: xMax, + min: xMin, + /*displayFormats: { + hour: 'MMM D, hA' + }*/ + }, + }], + yAxes: [{ + display: true, + ticks: { + min: yMin, + max: yMax, + callback: (value, index, values) => { + return `${value.toFixed(2)}${yMeasurement}`; + } + } + }] + }, + legend: { + display: false + } + }, + }); + } + + componentWillReceiveProps(nextProps) { + + const { + data, + xMax, + xMin, + xUnitStepSize + } = this.processData(nextProps); + + this._chart.data.datasets = [{ + data + }]; + this._chart.options.scales.xAxes[0].time.max = xMax; + this._chart.options.scales.xAxes[0].time.min = xMin; + this._chart.options.scales.xAxes[0].time.unitStepSize = xUnitStepSize; + this._chart.update(0); + } + + processData(props) { + const { + data = [], + duration = 360 + } = this.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! + // All this shizzle below needs to be recalculated on new props, yay! + const now = moment(); + // 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() + } + ); + }); + + // set min and max + const xMax = now.toDate(); + const xMin = before.toDate(); + // calculate stepsize + const xUnitStepSize = duration/6; + + return { + data: dataWithTime, + xMax, + xMin, + xUnitStepSize + }; + } + + ref(name) { + this._refs = this._refs || {}; + + return (el) => { + this._refs[name] = el; + }; + } + + render() { + return ( + + + + ); + } +} + +Graph.propTypes = { + data: React.PropTypes.array, + duration: React.PropTypes.number, + yMax: React.PropTypes.number, + yMeasurement: React.PropTypes.string, + yMin: React.PropTypes.number +}; + +module.exports = Graph; diff --git a/ui/src/components/metric/icon-settings.svg b/ui/src/components/metric/icon-settings.svg new file mode 100644 index 00000000..322ce7ea --- /dev/null +++ b/ui/src/components/metric/icon-settings.svg @@ -0,0 +1,18 @@ + + + + icon: settings + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/ui/src/components/metric/index.js b/ui/src/components/metric/index.js index d1090a77..ddd4f573 100644 --- a/ui/src/components/metric/index.js +++ b/ui/src/components/metric/index.js @@ -1,5 +1,5 @@ module.exports = { - MetricBody: require('./body'), + MetricGraph: require('./graph'), MetricCloseButton: require('./close-button'), MetricHeader: require('./header'), MetricSelect: require('./select'), diff --git a/ui/src/components/metric/metric-data.js b/ui/src/components/metric/metric-data.js new file mode 100644 index 00000000..6c1ff682 --- /dev/null +++ b/ui/src/components/metric/metric-data.js @@ -0,0 +1,307 @@ +module.exports = [{ + 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, +}, { + 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, +}]; diff --git a/ui/src/components/metric/select.js b/ui/src/components/metric/select.js index b257cccd..04690ba9 100644 --- a/ui/src/components/metric/select.js +++ b/ui/src/components/metric/select.js @@ -59,8 +59,10 @@ const Select = ({ form, id = rndId(), name, + onChange, required, - selected + selected, + value }) => { return ( @@ -70,8 +72,10 @@ const Select = ({ form={form} id={id} name={name} + onChange={onChange} required={required} selected={selected} + value={value} > {children} @@ -86,8 +90,10 @@ Select.propTypes = { form: React.PropTypes.string, id: React.PropTypes.string, name: React.PropTypes.string, + onChange: React.PropTypes.func, required: React.PropTypes.bool, - selected: React.PropTypes.bool + selected: React.PropTypes.bool, + value: React.PropTypes.string }; module.exports = Select; diff --git a/ui/src/components/metric/settings-button.js b/ui/src/components/metric/settings-button.js index 9e25e08f..8097d09e 100644 --- a/ui/src/components/metric/settings-button.js +++ b/ui/src/components/metric/settings-button.js @@ -3,7 +3,8 @@ const Styled = require('styled-components'); const fns = require('../../shared/functions'); const constants = require('../../shared/constants'); const Button = require('../button'); -const SettingsIcon = require('!babel!svg-react!./close.svg?name=SettingsIcon'); +const SettingsIcon = + require('!babel!svg-react!./icon-settings.svg?name=SettingsIcon'); const { default: styled diff --git a/ui/src/components/metric/story.js b/ui/src/components/metric/story.js index 6a8f2504..fd38a2c7 100644 --- a/ui/src/components/metric/story.js +++ b/ui/src/components/metric/story.js @@ -6,7 +6,7 @@ const { } = require('@kadira/storybook'); const { - MetricBody, + MetricGraph, MetricCloseButton, MetricHeader, MetricSelect, @@ -15,7 +15,42 @@ const { MetricView } = require('./'); +const MetricData = require('./metric-data'); + const onButtonClick = () => {}; +const onMetricSelect = () => {}; + +const hour = 60; // in minutes - for moment +const sixHours = 6*hour; +const twelveHours = 12*hour; +const oneDay = 24*hour; +const twoDays = 48*hour; + +const withinRange = ( + value, + newMin, + newMax, + precision = 2, + oldMin = 0, + oldMax = 100 +) => { + const normalisedValue = value-oldMin; + const newRange = newMax-newMin; + const oldRange = oldMax-oldMin; + const newValue = newMin + normalisedValue*newRange/oldRange; + return newValue.toFixed(2); +}; + +const percentageMetricData = MetricData; +const kbMetricData = MetricData.map(m => { + return { + firstQuartile: withinRange(m.firstQuartile, 1.55, 2.0), + thirdQuartile: withinRange(m.thirdQuartile, 1.55, 2.0), + median: withinRange(m.median, 1.55, 2.0), + max: withinRange(m.max, 1.55, 2.0), + min: withinRange(m.min, 1.55, 2.0), + }; +}); storiesOf('Metric', module) .add('Metric', () => ( @@ -23,16 +58,46 @@ storiesOf('Metric', module) Aggregated CPU usage - - - - - + + + + + - Settings + + Settings + - + + + + + Aggregated CPU usage + + + + + + + + Settings + + + + )); diff --git a/ui/src/components/metric/view.js b/ui/src/components/metric/view.js index e3a5c757..00e2b820 100644 --- a/ui/src/components/metric/view.js +++ b/ui/src/components/metric/view.js @@ -1,4 +1,5 @@ const constants = require('../../shared/constants'); +const fns = require('../../shared/functions'); const React = require('react'); const Styled = require('styled-components'); @@ -7,6 +8,10 @@ const { colors } = constants; +const { + remcalc +} = fns; + const { default: styled } = Styled; @@ -15,6 +20,7 @@ const Container = styled.div` position: relative; box-sizing: border-box; width: 100%; + max-width: ${remcalc(940)}; box-shadow: ${boxes.bottomShaddow}; border: 1px solid ${colors.borderSecondary}; `; diff --git a/ui/yarn.lock b/ui/yarn.lock index 685d46e8..c43e97ab 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -4343,7 +4343,7 @@ mobx@^2.3.4: version "2.7.0" resolved "https://registry.yarnpkg.com/mobx/-/mobx-2.7.0.tgz#cf3d82d18c0ca7f458d8f2a240817b3dc7e54a01" -moment@^2.10.6: +moment@^2.10.6, moment@^2.17.1: version "2.17.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.17.1.tgz#fed9506063f36b10f066c8b59a144d7faebe1d82"