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 @@
+
+
\ 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"