summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJose Ivan Vargas <jvargas@gitlab.com>2017-05-24 15:46:04 -0500
committerJose Ivan Vargas <jvargas@gitlab.com>2017-05-24 15:46:04 -0500
commitf7a821519e12301e7bcfb2b1f3a704f062ad6d91 (patch)
tree5eeb9e3412370afd8407d9156678ce815a566f4e
parentf4965ae3cb3f1603b3710cc36561a6e9dfa61722 (diff)
downloadgitlab-ce-support-additional-metrics-environments-dashboard.tar.gz
Added the responsive characteristic to the d3 graphs using thesupport-additional-metrics-environments-dashboard
viewBox and padding techniques
-rw-r--r--app/assets/javascripts/monitoring/components/monitoring_column.vue191
-rw-r--r--app/assets/javascripts/monitoring/components/monitoring_panel.vue37
-rw-r--r--app/assets/javascripts/monitoring/components/monitoring_row.vue10
-rw-r--r--app/assets/javascripts/monitoring/event_hub.js3
-rw-r--r--app/assets/javascripts/monitoring/monitoring_bundle.js58
-rw-r--r--app/assets/javascripts/monitoring/stores/monitoring_store.js20
-rw-r--r--app/assets/stylesheets/pages/environments.scss26
7 files changed, 206 insertions, 139 deletions
diff --git a/app/assets/javascripts/monitoring/components/monitoring_column.vue b/app/assets/javascripts/monitoring/components/monitoring_column.vue
index e28e9577990..5957bcb2ab5 100644
--- a/app/assets/javascripts/monitoring/components/monitoring_column.vue
+++ b/app/assets/javascripts/monitoring/components/monitoring_column.vue
@@ -1,10 +1,11 @@
<script>
+ /* global Breakpoints */
import d3 from 'd3';
import {
dateFormat,
timeFormat,
} from '../constants';
-
+ import eventHub from '../event_hub';
const bisectDate = d3.bisector(d => d[0]).left;
@@ -20,6 +21,11 @@
required: true,
default: 'col-md-6',
},
+ updateAspectRatio: {
+ type: Boolean,
+ required: true,
+ default: false,
+ },
},
data() {
return {
@@ -42,21 +48,38 @@
svgContainer: {},
data: [],
axisLabelContainer: {},
+ breakpointHandler: Breakpoints.get(),
};
},
methods: {
+ draw() {
+ const breakpointSize = this.breakpointHandler.getBreakpointSize();
+ let height = 500;
+ if (breakpointSize === 'xs' || breakpointSize === 'sm') {
+ height = 300;
+ }
+ this.svgContainer = this.$el.querySelector('svg');
+ this.data = (this.columnData.queries[0].result[0])[0].values;
+ this.width = this.svgContainer.clientWidth -
+ this.margin.left - this.margin.right;
+ this.height = height - this.margin.top - this.margin.bottom;
+ if (this.data !== undefined) {
+ this.renderAxisAndContainer();
+ this.renderLabelAxisContainer();
+ }
+ },
handleMouseOverGraph() {
const rectOverlay = this.$el.querySelector('.prometheus-graph-overlay');
const currentXCoordinate = d3.mouse(rectOverlay)[0];
- const timeValueOverlay = this.xScale.invert(currentXCoordinate);
+ const timeValueOverlay = this.xScale2.invert(currentXCoordinate);
const overlayIndex = bisectDate(this.data, timeValueOverlay, 1);
const d0 = this.data[overlayIndex - 1];
const d1 = this.data[overlayIndex];
- if(d0 === undefined || d1 === undefined) return;
+ if (d0 === undefined || d1 === undefined) return;
const evalTime = timeValueOverlay - d0[0] > d1[0] - timeValueOverlay;
const currentData = evalTime ? d1 : d0;
const currentDeployXPos = {};
- const currentTimeCoordinate = Math.floor(this.xScale(currentData[0]));
+ let currentTimeCoordinate = Math.floor(this.xScale2(currentData[0]));
// const currentDeployXPos = this.deployments.mouseOverDeployInfo(currentXCoordinate, key);
const maxValueFromData = d3.max(this.data.map(d => d[1]));
const maxMetricValue = this.yScale(maxValueFromData);
@@ -65,10 +88,12 @@
graphContainer.selectAll('.selected-metric-line').remove();
graphContainer.selectAll('.circle-metric').remove();
graphContainer.selectAll('.rect-text-metric:not(.deploy-info-rect)').remove();
+ graphContainer.select('.mouse-over-flag').remove();
// if (currentDeployXPos) return;
- const currentChart = graphContainer.select('g');
+ const currentChart = graphContainer.select('.graph-data')
+ .append('g').attr('class', 'mouse-over-flag');
currentChart.append('line')
.attr({
@@ -77,17 +102,23 @@
y1: this.yScale(0),
x2: currentTimeCoordinate,
y2: maxMetricValue,
- });
+ })
+ .attr('transform', 'translate(-5,0)');
currentChart.append('circle')
.attr('class', 'circle-metric')
.attr('fill', '#5b99f7')
.attr('cx', currentTimeCoordinate || currentDeployXPos)
.attr('cy', this.yScale(currentData[1]))
- .attr('r', 5);
+ .attr('r', 5)
+ .attr('transform', 'translate(-5,0)');
// The little box with text
- const rectTextMetric = graphContainer.select('g').append('svg')
+ if (currentTimeCoordinate >= this.width - 70 - 120) {
+ currentTimeCoordinate = currentTimeCoordinate -= 100;
+ }
+
+ const rectTextMetric = currentChart.append('svg')
.attr({
class: 'rect-text-metric',
x: currentTimeCoordinate,
@@ -102,6 +133,7 @@
rx: 2,
width: 90,
height: 40,
+ transform: 'translate(-5,0)',
});
rectTextMetric.append('text')
@@ -109,6 +141,7 @@
class: 'text-metric text-metric-bold',
x: 8,
y: 35,
+ transform: 'translate(-5,0)',
})
.text(timeFormat(new Date(currentData[0] * 1000)));
@@ -117,34 +150,29 @@
class: 'text-metric-date',
x: 8,
y: 15,
+ transform: 'translate(-5,0)',
})
.text(dateFormat(new Date(currentData[0] * 1000)));
},
renderAxisAndContainer() {
+ d3.select(this.$el.querySelector('.prometheus-svg-container'))
+ .attr({
+ style: `padding-bottom: ${(Math.ceil(this.height * 100) / this.width)}%`,
+ });
+
const chart = d3.select(this.svgContainer)
- .attr("preserveAspectRatio", "xMinYMin meet")
- .attr("viewBox", this.viewBoxSize)
- .attr('class', 'svg-content')
- .append('g')
- .attr('transform', `translate(${this.margin.left},${this.margin.top})`);
-
- this.width = this.svgContainer.viewBox.baseVal.width - this.margin.left - this.margin.right;
- this.height = this.svgContainer.viewBox.baseVal.height - this.margin.top - this.margin.bottom;
+ .attr('viewBox', `0 0 ${this.width} ${this.height}`);
+
this.xScale = d3.time.scale()
.range([0, this.width]);
this.yScale = d3.scale.linear()
- .range([this.height, 0]);
- // this.xScale.domain(d3.extent(this.data, d => d[0]));
- this.xScale.domain(d3.extent(this.data, function(d) {
- if (d !== undefined) {
- return d[0];
- }
- }));
+ .range([this.height - 100, 0]);
+ this.xScale.domain(d3.extent(this.data, d => d[0]));
this.yScale.domain([0, d3.max(this.data.map(d => d[1]))]);
const xAxis = d3.svg.axis()
.scale(this.xScale)
- .ticks(3)
+ .ticks(5)
.orient('bottom');
const yAxis = d3.svg.axis()
@@ -154,49 +182,66 @@
chart.append('g')
.attr('class', 'x-axis')
- .attr('transform', `translate(0,${this.height})`)
+ .attr('transform', `translate(70,${this.height - 100})`)
.call(xAxis);
+ const width = this.width;
chart.append('g')
.attr('class', 'y-axis')
- .call(yAxis);
+ .attr('transform', 'translate(70,0)')
+ .call(yAxis)
+ .selectAll('.tick')
+ .each(function createTickLines() {
+ d3.select(this).select('line').attr('x2', width);
+ }); // This will select all of the ticks once they're rendered
+
+ const pathGroup = chart.append('svg')
+ .attr('class', 'graph-data')
+ .attr('viewBox', `0 0 ${this.width - 150} ${this.height}`);
+
+ this.xScale2 = d3.time.scale()
+ .range([0, this.width - 70]);
+
+ this.xScale2.domain(d3.extent(this.data, d => d[0]));
const area = d3.svg.area()
- .x(d => this.xScale(d[0]))
- .y0(this.height)
+ .x(d => this.xScale2(d[0]))
+ .y0(this.height - 100)
.y1(d => this.yScale(d[1]))
.interpolate('linear');
const line = d3.svg.line()
- .x(d => this.xScale(d[0]))
+ .x(d => this.xScale2(d[0]))
.y(d => this.yScale(d[1]));
- chart.append('path')
+ pathGroup.append('path')
.datum(this.data)
.attr('d', area)
.attr('class', 'metric-area')
- .attr('fill', '#edf3fc');
+ .attr('fill', '#edf3fc')
+ .attr('transform', 'translate(-5,0)');
- chart.append('path')
+ pathGroup.append('path')
.datum(this.data)
.attr('class', 'metric-line')
.attr('stroke', '#5b99f7')
.attr('fill', 'none')
.attr('stroke-width', 2)
- .attr('d', line);
+ .attr('d', line)
+ .attr('transform', 'translate(-5, 0)');
- //Overlay area for mouseover events
- chart.append('rect')
+ // Overlay area for mouseover events
+ pathGroup.append('rect')
.attr('class', 'prometheus-graph-overlay')
- .attr('width', this.width)
- .attr('height', this.height)
+ .attr('width', this.width - 70)
+ .attr('height', this.height - 100)
+ .attr('transform', 'translate(-5, 0)')
.on('mousemove', this.handleMouseOverGraph);
},
renderLabelAxisContainer() {
const axisLabelContainer = d3.select(this.svgContainer)
.append('g')
- .attr('class', 'axis-label-container')
- .attr('transform', `translate(${this.marginLabelContainer.left},${this.marginLabelContainer.top})`);
+ .attr('class', 'axis-label-container');
axisLabelContainer.append('line')
.attr('class', 'label-x-axis-line')
@@ -204,9 +249,9 @@
.attr('stroke-width', '1')
.attr({
x1: 10,
- y1: this.svgContainer.viewBox.baseVal.height - this.margin.top,
- x2: (this.svgContainer.viewBox.baseVal.width - this.margin.right) + 10,
- y2: this.svgContainer.viewBox.baseVal.height - this.margin.top,
+ y1: (this.height - this.margin.top) + 20,
+ x2: this.width + 20,
+ y2: (this.height - this.margin.top) + 20,
});
axisLabelContainer.append('line')
@@ -217,7 +262,7 @@
x1: 10,
y1: 0,
x2: 10,
- y2: this.svgContainer.viewBox.baseVal.height - this.margin.top,
+ y2: (this.height - this.margin.top) + 20,
});
axisLabelContainer.append('rect')
@@ -230,55 +275,65 @@
axisLabelContainer.append('text')
.attr('class', 'label-axis-text')
.attr('text-anchor', 'middle')
- .attr('transform', `translate(15, ${(this.svgContainer.viewBox.baseVal.height - this.margin.top) / 2}) rotate(-90)`)
+ .attr('transform', `translate(15, ${((this.height - this.margin.top) + 20) / 2}) rotate(-90)`)
.text('I.O.U Title'); // TODO: Put the appropiate title
axisLabelContainer.append('rect')
.attr('class', 'rect-axis-text')
- .attr('x', (this.svgContainer.viewBox.baseVal.width / 2) - this.margin.right)
- .attr('y', this.svgContainer.viewBox.baseVal.height - 100)
+ .attr('x', ((this.width + 20) / 2) - this.margin.right)
+ .attr('y', this.height - 80)
.attr('width', 30)
- .attr('height', 80);
+ .attr('height', 50);
axisLabelContainer.append('text')
.attr('class', 'label-axis-text')
- .attr('x', (this.svgContainer.viewBox.baseVal.width / 2) - this.margin.right)
- .attr('y', this.svgContainer.viewBox.baseVal.height - this.margin.top)
+ .attr('x', ((this.width + 20) / 2) - this.margin.right)
+ .attr('y', (this.height - this.margin.top) + 20)
.attr('dy', '.35em')
.text('Time');
- // TODO: Move this to the bottom of the graph, these are the legends
+ // The legends
axisLabelContainer.append('rect')
- .attr('x', this.svgContainer.viewBox.baseVal.width - 170)
- .attr('y', (this.svgContainer.viewBox.baseVal.height / 2) - 60)
+ .attr('x', 20)
+ .attr('y', this.height - 55)
.style('fill', '#edf3fc')
.attr('width', 20)
.attr('height', 35);
axisLabelContainer.append('text')
.attr('class', 'text-metric-title')
- .attr('x', this.svgContainer.viewBox.baseVal.width - 140)
- .attr('y', (this.svgContainer.viewBox.baseVal.height / 2) - 50)
+ .attr('x', 50)
+ .attr('y', this.height - 40)
.text('Average');
axisLabelContainer.append('text')
.attr('class', 'text-metric-usage')
- .attr('x', this.svgContainer.viewBox.baseVal.width - 140)
- .attr('y', (this.svgContainer.viewBox.baseVal.height / 2) - 25);
- }
+ .attr('x', 50)
+ .attr('y', this.height - 25)
+ .text('N/A');
+ },
+ redraw() {
+ // Remove event listeners and graphs, then redraw them
+ d3.select(this.svgContainer).select('.prometheus-graph-overlay').on('mousemove', null);
+ d3.select(this.svgContainer).remove();
+ d3.select(this.$el).select('.prometheus-svg-container').append('svg');
+ this.draw();
+ },
},
+
+ watch: {
+ updateAspectRatio: {
+ handler() {
+ if (this.updateAspectRatio) {
+ this.redraw();
+ eventHub.$emit('toggleAspectRatio');
+ }
+ },
+ },
+ },
+
mounted() {
- this.svgContainer = this.$el.querySelector('svg');
- this.data = (this.columnData.queries[0].result[0])[0].values || (this.columnData.queries[0].result[0])[0].value;
- if(this.classType === 'col-md-6') {
- this.viewBoxSize = '0 0 600 350';
- } else {
- this.viewBoxSize = '0 0 500 250';
- }
- if (this.data !== undefined) {
- this.renderAxisAndContainer();
- this.renderLabelAxisContainer();
- }
+ this.draw();
},
};
</script>
diff --git a/app/assets/javascripts/monitoring/components/monitoring_panel.vue b/app/assets/javascripts/monitoring/components/monitoring_panel.vue
deleted file mode 100644
index e69544efbbb..00000000000
--- a/app/assets/javascripts/monitoring/components/monitoring_panel.vue
+++ /dev/null
@@ -1,37 +0,0 @@
-<script>
-import MonitoringRow from './monitoring_row.vue';
-/*
-This component renders a panel for each group of the correspondent metrics
-previously configured
-*/
-export default {
- props: {
- groupData: {
- type: Object,
- required: true,
- default: {},
- },
- },
- components: {
- 'monitoring-row': MonitoringRow
- }
-};
-</script>
-<template>
- <div class="row">
- <div class="col-md-12">
- <div class="panel panel-default prometheus-panel">
- <div class="panel-heading">
- <h4>{{groupData.group}}</h4>
- </div>
- <div class="panel-body">
- <monitoring-row
- v-for="(row, index) in groupData.metrics"
- :rowData="row"
- :key="index"
- />
- </div>
- </div>
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/monitoring/components/monitoring_row.vue b/app/assets/javascripts/monitoring/components/monitoring_row.vue
index db11d756b60..bd72e3fb3a3 100644
--- a/app/assets/javascripts/monitoring/components/monitoring_row.vue
+++ b/app/assets/javascripts/monitoring/components/monitoring_row.vue
@@ -8,13 +8,18 @@
required: true,
default: () => [],
},
+ updateAspectRatio: {
+ type: Boolean,
+ required: true,
+ default: false,
+ },
},
components: {
'monitoring-column': MonitoringColumn,
},
methods: {
bootstrapClass() {
- return this.rowData.length > 3 ? 'col-md-6' : 'col-md-12';
+ return this.rowData.length >= 3 ? 'col-md-6' : 'col-md-12';
},
},
};
@@ -25,7 +30,8 @@
v-for="(column, index) in rowData"
:columnData="column"
:classType="bootstrapClass()"
- :key="index"
+ :key="index"
+ :updateAspectRatio="updateAspectRatio"
/>
</div>
</template>
diff --git a/app/assets/javascripts/monitoring/event_hub.js b/app/assets/javascripts/monitoring/event_hub.js
new file mode 100644
index 00000000000..0948c2e5352
--- /dev/null
+++ b/app/assets/javascripts/monitoring/event_hub.js
@@ -0,0 +1,3 @@
+import Vue from 'vue';
+
+export default new Vue();
diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js
index 6fc83d76b0c..880c6de2a99 100644
--- a/app/assets/javascripts/monitoring/monitoring_bundle.js
+++ b/app/assets/javascripts/monitoring/monitoring_bundle.js
@@ -7,8 +7,9 @@ import VueResource from 'vue-resource';
import d3 from 'd3';
import MonitoringService from './services/monitoring_service';
import MonitoringState from './components/monitoring_state.vue';
-import MonitoringPanel from './components/monitoring_panel.vue';
+import MonitoringRow from './components/monitoring_row.vue';
import MonitoringStore from './stores/monitoring_store';
+import eventHub from './event_hub';
import MonitoringNewAPIResponse from './monitoring_mock';
Vue.use(VueResource);
@@ -37,12 +38,14 @@ document.addEventListener('DOMContentLoaded', function onLoad() {
backOffRequestCounter: 0,
bisectDate: d3.bisector(d => d.time).left,
service: {},
+ updateAspectRatio: false,
+ updatedAspectRatios: 0,
};
},
components: {
'monitoring-state': MonitoringState,
- 'monitoring-panel': MonitoringPanel,
+ 'monitoring-row': MonitoringRow,
},
methods: {
@@ -89,13 +92,31 @@ document.addEventListener('DOMContentLoaded', function onLoad() {
this.showEmptyState = false;
this.store.storeMetrics(MonitoringNewAPIResponse);
},
- increaseValue() {
- this.testValue = this.testValue += 1;
+ resizeThrottler() {
+ // ignore resize events as long as an actualResizeHandler execution is in the queue
+ if (!this.resizeTimeout) {
+ this.resizeTimeout = setTimeout(() => {
+ this.resizeTimeout = null;
+ this.updateAspectRatio = true;
+ }, 600);
+ }
+ },
+ toggleAspectRatio() {
+ this.updatedAspectRatios = this.updatedAspectRatios += 1;
+ if (this.store.getMetricsCount() === this.updatedAspectRatios) {
+ this.updateAspectRatio = !this.updateAspectRatio;
+ this.updatedAspectRatios = 0;
+ }
},
},
created() {
this.service = new MonitoringService();
+ eventHub.$on('toggleAspectRatio', this.toggleAspectRatio);
+ },
+
+ beforeDestroyed() {
+ eventHub.$off('toggleAspectRatio');
},
mounted() {
@@ -106,19 +127,31 @@ document.addEventListener('DOMContentLoaded', function onLoad() {
} else {
// this.getGraphsData();
this.getGraphsDataNewApiMock();
+ window.addEventListener('resize', this.resizeThrottler, false);
}
},
template: `
<div class="prometheus-graphs" v-if="!showEmptyState">
- <button class="btn btn-primary" @click=increaseValue>
- Increase test value
- </button>
- <monitoring-panel
- v-for="(group, index) in store.groups"
- :groupData="group"
- :key="index"
- />
+ <div class="row"
+ v-for="(groupData, index) in store.groups"
+ >
+ <div class="col-md-12">
+ <div class="panel panel-default prometheus-panel">
+ <div class="panel-heading">
+ <h4>{{groupData.group}}</h4>
+ </div>
+ <div class="panel-body">
+ <monitoring-row
+ v-for="(row, index) in groupData.metrics"
+ :rowData="row"
+ :key="index"
+ :updateAspectRatio="updateAspectRatio"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
</div>
<monitoring-state
:isLoading=isLoading
@@ -128,4 +161,3 @@ document.addEventListener('DOMContentLoaded', function onLoad() {
`,
});
}, false);
-
diff --git a/app/assets/javascripts/monitoring/stores/monitoring_store.js b/app/assets/javascripts/monitoring/stores/monitoring_store.js
index 53cca6ee9a3..0137a1c803a 100644
--- a/app/assets/javascripts/monitoring/stores/monitoring_store.js
+++ b/app/assets/javascripts/monitoring/stores/monitoring_store.js
@@ -2,9 +2,11 @@ import _ from 'underscore';
class MonitoringStore {
constructor() {
- this.groups = [];
- this.enoughMetrics = true;
- return this;
+ if (!MonitoringStore.singleton) {
+ this.groups = [];
+ this.enoughMetrics = true;
+ }
+ return MonitoringStore.singleton;
}
// TODO: Probably move this to an utility class
@@ -27,7 +29,7 @@ class MonitoringStore {
}
storeMetrics(groups = []) {
- // we're going to have to sort the groups depending on the weight of each of the graphs
+ // TODO: Sorted by weight add the name as another modifier
this.groups = groups.map((group) => {
const currentGroup = group;
currentGroup.metrics = _.sortBy(group.metrics, 'weight');
@@ -35,6 +37,16 @@ class MonitoringStore {
return currentGroup;
});
}
+
+ getMetricsCount() {
+ let metricsCount = 0;
+ this.groups.forEach((group) => {
+ group.metrics.forEach((metric) => {
+ metricsCount = metricsCount += metric.length;
+ });
+ });
+ return metricsCount;
+ }
}
export default MonitoringStore;
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 11f034989eb..f9310bca6e3 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -232,27 +232,23 @@
}
.prometheus-svg-container {
- @media(max-width: $screen-sm-max) {
- display: inline-block;
- position: relative;
- width: 100%;
- padding-bottom: 60%;
- vertical-align: top;
- overflow: hidden;
- }
+ position: relative;
+ height: 0;
+ width: 100%;
+ padding: 0;
+ padding-bottom: 100%;
.text-metric-bold {
font-weight: 600;
}
}
-.svg-content {
- @media(max-width: $screen-sm-max) {
- display: inline-block;
- position: absolute;
- top: 0;
- left: 0;
- }
+.prometheus-svg-container > svg {
+ position: absolute;
+ height: 100%;
+ width: 100%;
+ left: 0;
+ top: 0;
text {
fill: $gl-text-color;