Commit 5870fc5d authored by Tim Zallmann's avatar Tim Zallmann

Merge branch 'cherry-pick-5ea675be' into 'master'

Merge branch 'additional-metrics-dashboard' into '28717-additional-metrics-review-branch'

See merge request !12454
parents 9ef6d028 dbec39ab
<script>
/* global Flash */
import _ from 'underscore';
import statusCodes from '../../lib/utils/http_status';
import MonitoringService from '../services/monitoring_service';
import monitoringRow from './monitoring_row.vue';
import monitoringState from './monitoring_state.vue';
import MonitoringStore from '../stores/monitoring_store';
import eventHub from '../event_hub';
export default {
data() {
const metricsData = document.querySelector('#prometheus-graphs').dataset;
const store = new MonitoringStore();
return {
store,
state: 'gettingStarted',
hasMetrics: gl.utils.convertPermissionToBoolean(metricsData.hasMetrics),
documentationPath: metricsData.documentationPath,
settingsPath: metricsData.settingsPath,
endpoint: metricsData.additionalMetrics,
deploymentEndpoint: metricsData.deploymentEndpoint,
showEmptyState: true,
backOffRequestCounter: 0,
updateAspectRatio: false,
updatedAspectRatios: 0,
resizeThrottled: {},
};
},
components: {
monitoringRow,
monitoringState,
},
methods: {
getGraphsData() {
const maxNumberOfRequests = 3;
this.state = 'loading';
gl.utils.backOff((next, stop) => {
this.service.get().then((resp) => {
if (resp.status === statusCodes.NO_CONTENT) {
this.backOffRequestCounter = this.backOffRequestCounter += 1;
if (this.backOffRequestCounter < maxNumberOfRequests) {
next();
} else {
stop(new Error('Failed to connect to the prometheus server'));
}
} else {
stop(resp);
}
}).catch(stop);
})
.then((resp) => {
if (resp.status === statusCodes.NO_CONTENT) {
this.state = 'unableToConnect';
return false;
}
return resp.json();
})
.then((metricGroupsData) => {
if (!metricGroupsData) return false;
this.store.storeMetrics(metricGroupsData.data);
return this.getDeploymentData();
})
.then((deploymentData) => {
if (deploymentData !== false) {
this.store.storeDeploymentData(deploymentData.deployments);
this.showEmptyState = false;
}
return {};
})
.catch(() => {
this.state = 'unableToConnect';
});
},
getDeploymentData() {
return this.service.getDeploymentData(this.deploymentEndpoint)
.then(resp => resp.json())
.catch(() => new Flash('Error getting deployment information.'));
},
resize() {
this.updateAspectRatio = true;
},
toggleAspectRatio() {
this.updatedAspectRatios = this.updatedAspectRatios += 1;
if (this.store.getMetricsCount() === this.updatedAspectRatios) {
this.updateAspectRatio = !this.updateAspectRatio;
this.updatedAspectRatios = 0;
}
},
},
created() {
this.service = new MonitoringService(this.endpoint);
eventHub.$on('toggleAspectRatio', this.toggleAspectRatio);
},
beforeDestroy() {
eventHub.$off('toggleAspectRatio', this.toggleAspectRatio);
window.removeEventListener('resize', this.resizeThrottled, false);
},
mounted() {
this.resizeThrottled = _.throttle(this.resize, 600);
if (!this.hasMetrics) {
this.state = 'gettingStarted';
} else {
this.getGraphsData();
window.addEventListener('resize', this.resizeThrottled, false);
}
},
};
</script>
<template>
<div
class="prometheus-graphs"
v-if="!showEmptyState">
<div
class="row"
v-for="(groupData, index) in store.groups"
:key="index">
<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"
:key="index"
:row-data="row"
:update-aspect-ratio="updateAspectRatio"
:deployment-data="store.deploymentData"
/>
</div>
</div>
</div>
</div>
</div>
<monitoring-state
:selected-state="state"
:documentation-path="documentationPath"
:settings-path="settingsPath"
v-else
/>
</template>
<script>
/* global Breakpoints */
import d3 from 'd3';
import monitoringLegends from './monitoring_legends.vue';
import monitoringFlag from './monitoring_flag.vue';
import monitoringDeployment from './monitoring_deployment.vue';
import MonitoringMixin from '../mixins/monitoring_mixins';
import eventHub from '../event_hub';
import measurements from '../utils/measurements';
import { formatRelevantDigits } from '../../lib/utils/number_utils';
const bisectDate = d3.bisector(d => d.time).left;
export default {
props: {
columnData: {
type: Object,
required: true,
},
classType: {
type: String,
required: true,
},
updateAspectRatio: {
type: Boolean,
required: true,
},
deploymentData: {
type: Array,
required: true,
},
},
mixins: [MonitoringMixin],
data() {
return {
graphHeight: 500,
graphWidth: 600,
graphHeightOffset: 120,
xScale: {},
yScale: {},
margin: {},
data: [],
breakpointHandler: Breakpoints.get(),
unitOfDisplay: '',
areaColorRgb: '#8fbce8',
lineColorRgb: '#1f78d1',
yAxisLabel: '',
legendTitle: '',
reducedDeploymentData: [],
area: '',
line: '',
measurements: measurements.large,
currentData: {
time: new Date(),
value: 0,
},
currentYCoordinate: 0,
currentXCoordinate: 0,
currentFlagPosition: 0,
metricUsage: '',
showFlag: false,
showDeployInfo: true,
};
},
components: {
monitoringLegends,
monitoringFlag,
monitoringDeployment,
},
computed: {
outterViewBox() {
return `0 0 ${this.graphWidth} ${this.graphHeight}`;
},
innerViewBox() {
if ((this.graphWidth - 150) > 0) {
return `0 0 ${this.graphWidth - 150} ${this.graphHeight}`;
}
return '0 0 0 0';
},
axisTransform() {
return `translate(70, ${this.graphHeight - 100})`;
},
paddingBottomRootSvg() {
return (Math.ceil(this.graphHeight * 100) / this.graphWidth) || 0;
},
},
methods: {
draw() {
const breakpointSize = this.breakpointHandler.getBreakpointSize();
const query = this.columnData.queries[0];
this.margin = measurements.large.margin;
if (breakpointSize === 'xs' || breakpointSize === 'sm') {
this.graphHeight = 300;
this.margin = measurements.small.margin;
this.measurements = measurements.small;
}
this.data = query.result[0].values;
this.unitOfDisplay = query.unit || 'N/A';
this.yAxisLabel = this.columnData.y_axis || 'Values';
this.legendTitle = query.legend || 'Average';
this.graphWidth = this.$refs.baseSvg.clientWidth -
this.margin.left - this.margin.right;
this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom;
if (this.data !== undefined) {
this.renderAxesPaths();
this.formatDeployments();
}
},
handleMouseOverGraph(e) {
let point = this.$refs.graphData.createSVGPoint();
point.x = e.clientX;
point.y = e.clientY;
point = point.matrixTransform(this.$refs.graphData.getScreenCTM().inverse());
point.x = point.x += 7;
const timeValueOverlay = this.xScale.invert(point.x);
const overlayIndex = bisectDate(this.data, timeValueOverlay, 1);
const d0 = this.data[overlayIndex - 1];
const d1 = this.data[overlayIndex];
if (d0 === undefined || d1 === undefined) return;
const evalTime = timeValueOverlay - d0[0] > d1[0] - timeValueOverlay;
this.currentData = evalTime ? d1 : d0;
this.currentXCoordinate = Math.floor(this.xScale(this.currentData.time));
const currentDeployXPos = this.mouseOverDeployInfo(point.x);
this.currentYCoordinate = this.yScale(this.currentData.value);
if (this.currentXCoordinate > (this.graphWidth - 200)) {
this.currentFlagPosition = this.currentXCoordinate - 103;
} else {
this.currentFlagPosition = this.currentXCoordinate;
}
if (currentDeployXPos) {
this.showFlag = false;
} else {
this.showFlag = true;
}
this.metricUsage = `${formatRelevantDigits(this.currentData.value)} ${this.unitOfDisplay}`;
},
renderAxesPaths() {
const axisXScale = d3.time.scale()
.range([0, this.graphWidth]);
this.yScale = d3.scale.linear()
.range([this.graphHeight - this.graphHeightOffset, 0]);
axisXScale.domain(d3.extent(this.data, d => d.time));
this.yScale.domain([0, d3.max(this.data.map(d => d.value))]);
const xAxis = d3.svg.axis()
.scale(axisXScale)
.ticks(measurements.ticks)
.orient('bottom');
const yAxis = d3.svg.axis()
.scale(this.yScale)
.ticks(measurements.ticks)
.orient('left');
d3.select(this.$refs.baseSvg).select('.x-axis').call(xAxis);
const width = this.graphWidth;
d3.select(this.$refs.baseSvg).select('.y-axis').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
this.xScale = d3.time.scale()
.range([0, this.graphWidth - 70]);
this.xScale.domain(d3.extent(this.data, d => d.time));
const areaFunction = d3.svg.area()
.x(d => this.xScale(d.time))
.y0(this.graphHeight - this.graphHeightOffset)
.y1(d => this.yScale(d.value))
.interpolate('linear');
const lineFunction = d3.svg.line()
.x(d => this.xScale(d.time))
.y(d => this.yScale(d.value));
this.line = lineFunction(this.data);
this.area = areaFunction(this.data);
},
},
watch: {
updateAspectRatio() {
if (this.updateAspectRatio) {
this.graphHeight = 500;
this.graphWidth = 600;
this.measurements = measurements.large;
this.draw();
eventHub.$emit('toggleAspectRatio');
}
},
},
mounted() {
this.draw();
},
};
</script>
<template>
<div
:class="classType">
<h5
class="text-center">
{{columnData.title}}
</h5>
<div
class="prometheus-svg-container">
<svg
:viewBox="outterViewBox"
:style="{ 'padding-bottom': paddingBottomRootSvg }"
ref="baseSvg">
<g
class="x-axis"
:transform="axisTransform">
</g>
<g
class="y-axis"
transform="translate(70, 20)">
</g>
<monitoring-legends
:graph-width="graphWidth"
:graph-height="graphHeight"
:margin="margin"
:measurements="measurements"
:area-color-rgb="areaColorRgb"
:legend-title="legendTitle"
:y-axis-label="yAxisLabel"
:metric-usage="metricUsage"
/>
<svg
class="graph-data"
:viewBox="innerViewBox"
ref="graphData">
<path
class="metric-area"
:d="area"
:fill="areaColorRgb"
transform="translate(-5, 20)">
</path>
<path
class="metric-line"
:d="line"
:stroke="lineColorRgb"
fill="none"
stroke-width="2"
transform="translate(-5, 20)">
</path>
<rect
class="prometheus-graph-overlay"
:width="(graphWidth - 70)"
:height="(graphHeight - 100)"
transform="translate(-5, 20)"
ref="graphOverlay"
@mousemove="handleMouseOverGraph($event)">
</rect>
<monitoring-deployment
:show-deploy-info="showDeployInfo"
:deployment-data="reducedDeploymentData"
:graph-height="graphHeight"
:graph-height-offset="graphHeightOffset"
/>
<monitoring-flag
v-if="showFlag"
:current-x-coordinate="currentXCoordinate"
:current-y-coordinate="currentYCoordinate"
:current-data="currentData"
:current-flag-position="currentFlagPosition"
:graph-height="graphHeight"
:graph-height-offset="graphHeightOffset"
/>
</svg>
</svg>
</div>
</div>
</template>
<script>
import {
dateFormat,
timeFormat,
} from '../constants';
export default {
props: {
showDeployInfo: {
type: Boolean,
required: true,
},
deploymentData: {
type: Array,
required: true,
},
graphHeight: {
type: Number,
required: true,
},
graphHeightOffset: {
type: Number,
required: true,
},
},
computed: {
calculatedHeight() {
return this.graphHeight - this.graphHeightOffset;
},
},
methods: {
refText(d) {
return d.tag ? d.ref : d.sha.slice(0, 6);
},
formatTime(deploymentTime) {
return timeFormat(deploymentTime);
},
formatDate(deploymentTime) {
return dateFormat(deploymentTime);
},
nameDeploymentClass(deployment) {
return `deploy-info-${deployment.id}`;
},
transformDeploymentGroup(deployment) {
return `translate(${Math.floor(deployment.xPos) + 1}, 20)`;
},
},
};
</script>
<template>
<g
class="deploy-info"
v-if="showDeployInfo">
<g
v-for="(deployment, index) in deploymentData"
:key="index"
:class="nameDeploymentClass(deployment)"
:transform="transformDeploymentGroup(deployment)">
<rect
x="0"
y="0"
:height="calculatedHeight"
width="3"
fill="url(#shadow-gradient)">
</rect>
<line
class="deployment-line"
x1="0"
y1="0"
x2="0"
:y2="calculatedHeight"
stroke="#000">
</line>
<svg
v-if="deployment.showDeploymentFlag"
class="js-deploy-info-box"
x="3"
y="0"
width="92"
height="60">
<rect
class="rect-text-metric deploy-info-rect rect-metric"
x="1"
y="1"
rx="2"
width="90"
height="58">
</rect>
<g
transform="translate(5, 2)">
<text
class="deploy-info-text text-metric-bold">
{{refText(deployment)}}
</text>
</g>
<text
class="deploy-info-text"
y="18"
transform="translate(5, 2)">
{{formatDate(deployment.time)}}
</text>
<text
class="deploy-info-text text-metric-bold"
y="38"
transform="translate(5, 2)">
{{formatTime(deployment.time)}}
</text>
</svg>
</g>
<svg
height="0"
width="0">
<defs>
<linearGradient
id="shadow-gradient">
<stop
offset="0%"
stop-color="#000"
stop-opacity="0.4">
</stop>
<stop
offset="100%"
stop-color="#000"
stop-opacity="0">
</stop>
</linearGradient>
</defs>
</svg>
</g>
</template>
<script>
import {
dateFormat,
timeFormat,
} from '../constants';
export default {
props: {
currentXCoordinate: {
type: Number,
required: true,
},
currentYCoordinate: {
type: Number,
required: true,
},
currentFlagPosition: {
type: Number,
required: true,
},
currentData: {
type: Object,
required: true,
},
graphHeight: {
type: Number,
required: true,
},
graphHeightOffset: {
type: Number,
required: true,
},
},
data() {
return {
circleColorRgb: '#8fbce8',
};
},
computed: {
formatTime() {
return timeFormat(this.currentData.time);
},
formatDate() {
return dateFormat(this.currentData.time);
},
calculatedHeight() {
return this.graphHeight - this.graphHeightOffset;
},
},
};
</script>
<template>
<g class="mouse-over-flag">
<line
class="selected-metric-line"
:x1="currentXCoordinate"
:y1="0"
:x2="currentXCoordinate"
:y2="calculatedHeight"
transform="translate(-5, 20)">
</line>
<circle
class="circle-metric"
:fill="circleColorRgb"
stroke="#000"
:cx="currentXCoordinate"
:cy="currentYCoordinate"
r="5"
transform="translate(-5, 20)">
</circle>
<svg
class="rect-text-metric"
:x="currentFlagPosition"
y="0">
<rect
class="rect-metric"
x="4"
y="1"
rx="2"
width="90"
height="40"
transform="translate(-3, 20)">
</rect>
<text
class="text-metric text-metric-bold"
x="8"
y="35"
transform="translate(-5, 20)">
{{formatTime}}
</text>
<text
class="text-metric-date"
x="8"
y="15"
transform="translate(-5, 20)">
{{formatDate}}
</text>
</svg>
</g>
</template>
<script>
export default {
props: {
graphWidth: {
type: Number,
required: true,
},
graphHeight: {
type: Number,
required: true,
},
margin: {
type: Object,
required: true,
},
measurements: {
type: Object,
required: true,
},
areaColorRgb: {
type: String,
required: true,
},
legendTitle: {
type: String,
required: true,
},
yAxisLabel: {
type: String,
required: true,
},
metricUsage: {
type: String,
required: true,
},
},
data() {
return {
yLabelWidth: 0,
yLabelHeight: 0,
};
},
computed: {
textTransform() {
const yCoordinate = (((this.graphHeight - this.margin.top)
+ this.measurements.axisLabelLineOffset) / 2) || 0;
return `translate(15, ${yCoordinate}) rotate(-90)`;
},
rectTransform() {
const yCoordinate = ((this.graphHeight - this.margin.top) / 2)
+ (this.yLabelWidth / 2) + 10 || 0;
return `translate(0, ${yCoordinate}) rotate(-90)`;
},
xPosition() {
return (((this.graphWidth + this.measurements.axisLabelLineOffset) / 2)
- this.margin.right) || 0;
},
yPosition() {
return ((this.graphHeight - this.margin.top) + this.measurements.axisLabelLineOffset) || 0;
},
},
mounted() {
this.$nextTick(() => {
const bbox = this.$refs.ylabel.getBBox();
this.yLabelWidth = bbox.width + 10; // Added some padding
this.yLabelHeight = bbox.height + 5;
});
},
};
</script>
<template>
<g
class="axis-label-container">
<line
class="label-x-axis-line"
stroke="#000000"
stroke-width="1"
x1="10"
:y1="yPosition"
:x2="graphWidth + 20"
:y2="yPosition">
</line>
<line
class="label-y-axis-line"
stroke="#000000"
stroke-width="1"
x1="10"
y1="0"
:x2="10"
:y2="yPosition">
</line>
<rect
class="rect-axis-text"
:transform="rectTransform"
:width="yLabelWidth"
:height="yLabelHeight">
</rect>
<text
class="label-axis-text y-label-text"
text-anchor="middle"
:transform="textTransform"
ref="ylabel">
{{yAxisLabel}}
</text>
<rect
class="rect-axis-text"
:x="xPosition + 50"
:y="graphHeight - 80"
width="50"
height="50">
</rect>
<text
class="label-axis-text"
:x="xPosition + 60"
:y="yPosition"
dy=".35em">
Time
</text>
<rect
:fill="areaColorRgb"
:width="measurements.legends.width"
:height="measurements.legends.height"
x="20"
:y="graphHeight - measurements.legendOffset">
</rect>
<text
class="text-metric-title"
x="50"
:y="graphHeight - 40">
{{legendTitle}}
</text>
<text
class="text-metric-usage"
x="50"
:y="graphHeight - 25">
{{metricUsage}}
</text>
</g>
</template>
<script>
import monitoringColumn from './monitoring_column.vue';
export default {
props: {
rowData: {
type: Array,
required: true,
},
updateAspectRatio: {
type: Boolean,
required: true,
},
deploymentData: {
type: Array,
required: true,
},
},
components: {
monitoringColumn,
},
computed: {
bootstrapClass() {
return this.rowData.length >= 2 ? 'col-md-6' : 'col-md-12';
},
},
};
</script>
<template>
<div
class="prometheus-row row">
<monitoring-column
v-for="(column, index) in rowData"
:column-data="column"
:class-type="bootstrapClass"
:key="index"
:update-aspect-ratio="updateAspectRatio"
:deployment-data="deploymentData"
/>
</div>
</template>
<script>
import gettingStartedSvg from 'empty_states/monitoring/_getting_started.svg';
import loadingSvg from 'empty_states/monitoring/_loading.svg';
import unableToConnectSvg from 'empty_states/monitoring/_unable_to_connect.svg';
export default {
props: {
documentationPath: {
type: String,
required: true,
},
settingsPath: {
type: String,
required: false,
default: '',
},
selectedState: {
type: String,
required: true,
},
},
data() {
return {
states: {
gettingStarted: {
svg: gettingStartedSvg,
title: 'Get started with performance monitoring',
description: 'Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments.',
buttonText: 'Configure Prometheus',
},
loading: {
svg: loadingSvg,
title: 'Waiting for performance data',
description: 'Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available.',
buttonText: 'View documentation',
},
unableToConnect: {
svg: unableToConnectSvg,
title: 'Unable to connect to Prometheus server',
description: 'Ensure connectivity is available from the GitLab server to the ',
buttonText: 'View documentation',
},
},
};
},
computed: {
currentState() {
return this.states[this.selectedState];
},
buttonPath() {
if (this.selectedState === 'gettingStarted') {
return this.settingsPath;
}
return this.documentationPath;
},
showButtonDescription() {
if (this.selectedState === 'unableToConnect') return true;
return false;
},
},
};
</script>
<template>
<div
class="prometheus-state">
<div
class="row">
<div
class="col-md-4 col-md-offset-4 state-svg"
v-html="currentState.svg">
</div>
</div>
<div
class="row">
<div
class="col-md-6 col-md-offset-3">
<h4
class="text-center state-title">
{{currentState.title}}
</h4>
</div>
</div>
<div
class="row">
<div
class="col-md-6 col-md-offset-3">
<div
class="description-text text-center state-description">
{{currentState.description}}
<a
:href="settingsPath"
v-if="showButtonDescription">
Prometheus server
</a>
</div>
</div>
</div>
<div
class="row state-button-section">
<div
class="col-md-4 col-md-offset-4 text-center state-button">
<a
class="btn btn-success"
:href="buttonPath">
{{currentState.buttonText}}
</a>
</div>
</div>
</div>
</template>
/* global Flash */
import d3 from 'd3';
import {
dateFormat,
timeFormat,
} from './constants';
export default class Deployments {
constructor(width, height) {
this.width = width;
this.height = height;
this.endpoint = document.getElementById('js-metrics').dataset.deploymentEndpoint;
this.createGradientDef();
}
init(chartData) {
this.chartData = chartData;
this.x = d3.time.scale().range([0, this.width]);
this.x.domain(d3.extent(this.chartData, d => d.time));
this.charts = d3.selectAll('.prometheus-graph');
this.getData();
}
getData() {
$.ajax({
url: this.endpoint,
dataType: 'JSON',
})
.fail(() => new Flash('Error getting deployment information.'))
.done((data) => {
this.data = data.deployments.reduce((deploymentDataArray, deployment) => {
const time = new Date(deployment.created_at);
const xPos = Math.floor(this.x(time));
time.setSeconds(this.chartData[0].time.getSeconds());
if (xPos >= 0) {
deploymentDataArray.push({
id: deployment.id,
time,
sha: deployment.sha,
tag: deployment.tag,
ref: deployment.ref.name,
xPos,
});
}
return deploymentDataArray;
}, []);
this.plotData();
});
}
plotData() {
this.charts.each((d, i) => {
const svg = d3.select(this.charts[0][i]);
const chart = svg.select('.graph-container');
const key = svg.node().getAttribute('graph-type');
this.createLine(chart, key);
this.createDeployInfoBox(chart, key);
});
}
createGradientDef() {
const defs = d3.select('body')
.append('svg')
.attr({
height: 0,
width: 0,
})
.append('defs');
defs.append('linearGradient')
.attr({
id: 'shadow-gradient',
})
.append('stop')
.attr({
offset: '0%',
'stop-color': '#000',
'stop-opacity': 0.4,
})
.select(this.selectParentNode)
.append('stop')
.attr({
offset: '100%',
'stop-color': '#000',
'stop-opacity': 0,
});
}
createLine(chart, key) {
chart.append('g')
.attr({
class: 'deploy-info',
})
.selectAll('.deploy-info')
.data(this.data)
.enter()
.append('g')
.attr({
class: d => `deploy-info-${d.id}-${key}`,
transform: d => `translate(${Math.floor(d.xPos) + 1}, 0)`,
})
.append('rect')
.attr({
x: 1,
y: 0,
height: this.height + 1,
width: 3,
fill: 'url(#shadow-gradient)',
})
.select(this.selectParentNode)
.append('line')
.attr({
class: 'deployment-line',
x1: 0,
x2: 0,
y1: 0,
y2: this.height + 1,
});
}
createDeployInfoBox(chart, key) {
chart.selectAll('.deploy-info')
.selectAll('.js-deploy-info-box')
.data(this.data)
.enter()
.select(d => document.querySelector(`.deploy-info-${d.id}-${key}`))
.append('svg')
.attr({
class: 'js-deploy-info-box hidden',
x: 3,
y: 0,
width: 92,
height: 60,
})
.append('rect')
.attr({
class: 'rect-text-metric deploy-info-rect rect-metric',
x: 1,
y: 1,
rx: 2,
width: 90,
height: 58,
})
.select(this.selectParentNode)
.append('g')
.attr({
transform: 'translate(5, 2)',
})
.append('text')
.attr({
class: 'deploy-info-text text-metric-bold',
})
.text(Deployments.refText)
.select(this.selectParentNode)
.append('text')
.attr({
class: 'deploy-info-text',
y: 18,
})
.text(d => dateFormat(d.time))
.select(this.selectParentNode)
.append('text')
.attr({
class: 'deploy-info-text text-metric-bold',
y: 38,
})
.text(d => timeFormat(d.time));
}
static toggleDeployTextbox(deploy, key, showInfoBox) {
d3.selectAll(`.deploy-info-${deploy.id}-${key} .js-deploy-info-box`)
.classed('hidden', !showInfoBox);
}
mouseOverDeployInfo(mouseXPos, key) {
if (!this.data) return false;
let dataFound = false;
this.data.forEach((d) => {
if (d.xPos >= mouseXPos - 10 && d.xPos <= mouseXPos + 10 && !dataFound) {
dataFound = d.xPos + 1;
Deployments.toggleDeployTextbox(d, key, true);
} else {
Deployments.toggleDeployTextbox(d, key, false);
}
});
return dataFound;
}
/* `this` is bound to the D3 node */
selectParentNode() {
return this.parentNode;
}
static refText(d) {
return d.tag ? d.ref : d.sha.slice(0, 6);
}
}
import Vue from 'vue';
export default new Vue();
const mixins = {
methods: {
mouseOverDeployInfo(mouseXPos) {
if (!this.reducedDeploymentData) return false;
let dataFound = false;
this.reducedDeploymentData = this.reducedDeploymentData.map((d) => {
const deployment = d;
if (d.xPos >= mouseXPos - 10 && d.xPos <= mouseXPos + 10 && !dataFound) {
dataFound = d.xPos + 1;
deployment.showDeploymentFlag = true;
} else {
deployment.showDeploymentFlag = false;
}
return deployment;
});
return dataFound;
},
formatDeployments() {
this.reducedDeploymentData = this.deploymentData.reduce((deploymentDataArray, deployment) => {
const time = new Date(deployment.created_at);
const xPos = Math.floor(this.xScale(time));
time.setSeconds(this.data[0].time.getSeconds());
if (xPos >= 0) {
deploymentDataArray.push({
id: deployment.id,
time,
sha: deployment.sha,
tag: deployment.tag,
ref: deployment.ref.name,
xPos,
showDeploymentFlag: false,
});
}
return deploymentDataArray;
}, []);
},
},
};
export default mixins;
import PrometheusGraph from './prometheus_graph'; import Vue from 'vue';
import Monitoring from './components/monitoring.vue';
document.addEventListener('DOMContentLoaded', function onLoad() { document.addEventListener('DOMContentLoaded', () => new Vue({
document.removeEventListener('DOMContentLoaded', onLoad, false); el: '#prometheus-graphs',
return new PrometheusGraph(); components: {
}, false); 'monitoring-dashboard': Monitoring,
},
render: createElement => createElement('monitoring-dashboard'),
}));
/* eslint-disable no-new */
/* global Flash */
import d3 from 'd3';
import statusCodes from '~/lib/utils/http_status';
import Deployments from './deployments';
import '../lib/utils/common_utils';
import { formatRelevantDigits } from '../lib/utils/number_utils';
import '../flash';
import {
dateFormat,
timeFormat,
} from './constants';
const prometheusContainer = '.prometheus-container';
const prometheusParentGraphContainer = '.prometheus-graphs';
const prometheusGraphsContainer = '.prometheus-graph';
const prometheusStatesContainer = '.prometheus-state';
const metricsEndpoint = 'metrics.json';
const bisectDate = d3.bisector(d => d.time).left;
const extraAddedWidthParent = 100;
class PrometheusGraph {
constructor() {
const $prometheusContainer = $(prometheusContainer);
const hasMetrics = $prometheusContainer.data('has-metrics');
this.docLink = $prometheusContainer.data('doc-link');
this.integrationLink = $prometheusContainer.data('prometheus-integration');
this.state = '';
$(document).ajaxError(() => {});
if (hasMetrics) {
this.margin = { top: 80, right: 180, bottom: 80, left: 100 };
this.marginLabelContainer = { top: 40, right: 0, bottom: 40, left: 0 };
const parentContainerWidth = $(prometheusGraphsContainer).parent().width() +
extraAddedWidthParent;
this.originalWidth = parentContainerWidth;
this.originalHeight = 330;
this.width = parentContainerWidth - this.margin.left - this.margin.right;
this.height = this.originalHeight - this.margin.top - this.margin.bottom;
this.backOffRequestCounter = 0;
this.deployments = new Deployments(this.width, this.height);
this.configureGraph();
this.init();
} else {
const prevState = this.state;
this.state = '.js-getting-started';
this.updateState(prevState);
}
}
createGraph() {
Object.keys(this.graphSpecificProperties).forEach((key) => {
const value = this.graphSpecificProperties[key];
if (value.data.length > 0) {
this.plotValues(key);
}
});
}
init() {
return this.getData().then((metricsResponse) => {
let enoughData = true;
if (typeof metricsResponse === 'undefined') {
enoughData = false;
} else {
Object.keys(metricsResponse.metrics).forEach((key) => {
if (key === 'cpu_values' || key === 'memory_values') {
const currentData = (metricsResponse.metrics[key])[0];
if (currentData.values.length <= 2) {
enoughData = false;
}
}
});
}
if (enoughData) {
$(prometheusStatesContainer).hide();
$(prometheusParentGraphContainer).show();
this.transformData(metricsResponse);
this.createGraph();
const firstMetricData = this.graphSpecificProperties[
Object.keys(this.graphSpecificProperties)[0]
].data;
this.deployments.init(firstMetricData);
}
});
}
plotValues(key) {
const graphSpecifics = this.graphSpecificProperties[key];
const x = d3.time.scale()
.range([0, this.width]);
const y = d3.scale.linear()
.range([this.height, 0]);
graphSpecifics.xScale = x;
graphSpecifics.yScale = y;
const prometheusGraphContainer = `${prometheusGraphsContainer}[graph-type=${key}]`;
const chart = d3.select(prometheusGraphContainer)
.attr('width', this.width + this.margin.left + this.margin.right)
.attr('height', this.height + this.margin.bottom + this.margin.top)
.append('g')
.attr('class', 'graph-container')
.attr('transform', `translate(${this.margin.left},${this.margin.top})`);
const axisLabelContainer = d3.select(prometheusGraphContainer)
.attr('width', this.originalWidth)
.attr('height', this.originalHeight)
.append('g')
.attr('transform', `translate(${this.marginLabelContainer.left},${this.marginLabelContainer.top})`);
x.domain(d3.extent(graphSpecifics.data, d => d.time));
y.domain([0, d3.max(graphSpecifics.data.map(metricValue => metricValue.value))]);
const xAxis = d3.svg.axis()
.scale(x)
.ticks(this.commonGraphProperties.axis_no_ticks)
.orient('bottom');
const yAxis = d3.svg.axis()
.scale(y)
.ticks(this.commonGraphProperties.axis_no_ticks)
.tickSize(-this.width)
.outerTickSize(0)
.orient('left');
this.createAxisLabelContainers(axisLabelContainer, key);
chart.append('g')
.attr('class', 'x-axis')
.attr('transform', `translate(0,${this.height})`)
.call(xAxis);
chart.append('g')
.attr('class', 'y-axis')
.call(yAxis);
const area = d3.svg.area()
.x(d => x(d.time))
.y0(this.height)
.y1(d => y(d.value))
.interpolate('linear');
const line = d3.svg.line()
.x(d => x(d.time))
.y(d => y(d.value));
chart.append('path')
.datum(graphSpecifics.data)
.attr('d', area)
.attr('class', 'metric-area')
.attr('fill', graphSpecifics.area_fill_color);
chart.append('path')
.datum(graphSpecifics.data)
.attr('class', 'metric-line')
.attr('stroke', graphSpecifics.line_color)
.attr('fill', 'none')
.attr('stroke-width', this.commonGraphProperties.area_stroke_width)
.attr('d', line);
// Overlay area for the mouseover events
chart.append('rect')
.attr('class', 'prometheus-graph-overlay')
.attr('width', this.width)
.attr('height', this.height)
.on('mousemove', this.handleMouseOverGraph.bind(this, prometheusGraphContainer));
}
// The legends from the metric
createAxisLabelContainers(axisLabelContainer, key) {
const graphSpecifics = this.graphSpecificProperties[key];
axisLabelContainer.append('line')
.attr('class', 'label-x-axis-line')
.attr('stroke', '#000000')
.attr('stroke-width', '1')
.attr({
x1: 10,
y1: this.originalHeight - this.margin.top,
x2: (this.originalWidth - this.margin.right) + 10,
y2: this.originalHeight - this.margin.top,
});
axisLabelContainer.append('line')
.attr('class', 'label-y-axis-line')
.attr('stroke', '#000000')
.attr('stroke-width', '1')
.attr({
x1: 10,
y1: 0,
x2: 10,
y2: this.originalHeight - this.margin.top,
});
axisLabelContainer.append('rect')
.attr('class', 'rect-axis-text')
.attr('x', 0)
.attr('y', 50)
.attr('width', 30)
.attr('height', 150);
axisLabelContainer.append('text')
.attr('class', 'label-axis-text')
.attr('text-anchor', 'middle')
.attr('transform', `translate(15, ${(this.originalHeight - this.margin.top) / 2}) rotate(-90)`)
.text(graphSpecifics.graph_legend_title);
axisLabelContainer.append('rect')
.attr('class', 'rect-axis-text')
.attr('x', (this.originalWidth / 2) - this.margin.right)
.attr('y', this.originalHeight - 100)
.attr('width', 30)
.attr('height', 80);
axisLabelContainer.append('text')
.attr('class', 'label-axis-text')
.attr('x', (this.originalWidth / 2) - this.margin.right)
.attr('y', this.originalHeight - this.margin.top)
.attr('dy', '.35em')
.text('Time');
// Legends
// Metric Usage
axisLabelContainer.append('rect')
.attr('x', this.originalWidth - 170)
.attr('y', (this.originalHeight / 2) - 60)
.style('fill', graphSpecifics.area_fill_color)
.attr('width', 20)
.attr('height', 35);
axisLabelContainer.append('text')
.attr('class', 'text-metric-title')
.attr('x', this.originalWidth - 140)
.attr('y', (this.originalHeight / 2) - 50)
.text('Average');
axisLabelContainer.append('text')
.attr('class', 'text-metric-usage')
.attr('x', this.originalWidth - 140)
.attr('y', (this.originalHeight / 2) - 25);
}
handleMouseOverGraph(prometheusGraphContainer) {
const rectOverlay = document.querySelector(`${prometheusGraphContainer} .prometheus-graph-overlay`);
const currentXCoordinate = d3.mouse(rectOverlay)[0];
Object.keys(this.graphSpecificProperties).forEach((key) => {
const currentGraphProps = this.graphSpecificProperties[key];
const timeValueOverlay = currentGraphProps.xScale.invert(currentXCoordinate);
const overlayIndex = bisectDate(currentGraphProps.data, timeValueOverlay, 1);
const d0 = currentGraphProps.data[overlayIndex - 1];
const d1 = currentGraphProps.data[overlayIndex];
const evalTime = timeValueOverlay - d0.time > d1.time - timeValueOverlay;
const currentData = evalTime ? d1 : d0;
const currentTimeCoordinate = Math.floor(currentGraphProps.xScale(currentData.time));
const currentDeployXPos = this.deployments.mouseOverDeployInfo(currentXCoordinate, key);
const currentPrometheusGraphContainer = `${prometheusGraphsContainer}[graph-type=${key}]`;
const maxValueFromData = d3.max(currentGraphProps.data.map(metricValue => metricValue.value));
const maxMetricValue = currentGraphProps.yScale(maxValueFromData);
// Clear up all the pieces of the flag
d3.selectAll(`${currentPrometheusGraphContainer} .selected-metric-line`).remove();
d3.selectAll(`${currentPrometheusGraphContainer} .circle-metric`).remove();
d3.selectAll(`${currentPrometheusGraphContainer} .rect-text-metric:not(.deploy-info-rect)`).remove();
const currentChart = d3.select(currentPrometheusGraphContainer).select('g');
currentChart.append('line')
.attr({
class: `${currentDeployXPos ? 'hidden' : ''} selected-metric-line`,
x1: currentTimeCoordinate,
y1: currentGraphProps.yScale(0),
x2: currentTimeCoordinate,
y2: maxMetricValue,
});
currentChart.append('circle')
.attr('class', 'circle-metric')
.attr('fill', currentGraphProps.line_color)
.attr('cx', currentDeployXPos || currentTimeCoordinate)
.attr('cy', currentGraphProps.yScale(currentData.value))
.attr('r', this.commonGraphProperties.circle_radius_metric);
if (currentDeployXPos) return;
// The little box with text
const rectTextMetric = currentChart.append('svg')
.attr({
class: 'rect-text-metric',
x: currentTimeCoordinate,
y: 0,
});
rectTextMetric.append('rect')
.attr({
class: 'rect-metric',
x: 4,
y: 1,
rx: 2,
width: this.commonGraphProperties.rect_text_width,
height: this.commonGraphProperties.rect_text_height,
});
rectTextMetric.append('text')
.attr({
class: 'text-metric text-metric-bold',
x: 8,
y: 35,
})
.text(timeFormat(currentData.time));
rectTextMetric.append('text')
.attr({
class: 'text-metric-date',
x: 8,
y: 15,
})
.text(dateFormat(currentData.time));
let currentMetricValue = formatRelevantDigits(currentData.value);
if (key === 'cpu_values') {
currentMetricValue = `${currentMetricValue}%`;
} else {
currentMetricValue = `${currentMetricValue} MB`;
}
d3.select(`${currentPrometheusGraphContainer} .text-metric-usage`)
.text(currentMetricValue);
});
}
configureGraph() {
this.graphSpecificProperties = {
cpu_values: {
area_fill_color: '#edf3fc',
line_color: '#5b99f7',
graph_legend_title: 'CPU Usage (Cores)',
data: [],
xScale: {},
yScale: {},
},
memory_values: {
area_fill_color: '#fca326',
line_color: '#fc6d26',
graph_legend_title: 'Memory Usage (MB)',
data: [],
xScale: {},
yScale: {},
},
};
this.commonGraphProperties = {
area_stroke_width: 2,
median_total_characters: 8,
circle_radius_metric: 5,
rect_text_width: 90,
rect_text_height: 40,
axis_no_ticks: 3,
};
}
getData() {
const maxNumberOfRequests = 3;
this.state = '.js-loading';
this.updateState();
return gl.utils.backOff((next, stop) => {
$.ajax({
url: metricsEndpoint,
dataType: 'json',
})
.done((data, statusText, resp) => {
if (resp.status === statusCodes.NO_CONTENT) {
this.backOffRequestCounter = this.backOffRequestCounter += 1;
if (this.backOffRequestCounter < maxNumberOfRequests) {
next();
} else if (this.backOffRequestCounter >= maxNumberOfRequests) {
stop(new Error('loading'));
}
} else if (!data.success) {
stop(new Error('loading'));
} else {
stop({
status: resp.status,
metrics: data,
});
}
}).fail(stop);
})
.then((resp) => {
if (resp.status === statusCodes.NO_CONTENT) {
return {};
}
return resp.metrics;
})
.catch(() => {
const prevState = this.state;
this.state = '.js-unable-to-connect';
this.updateState(prevState);
});
}
transformData(metricsResponse) {
Object.keys(metricsResponse.metrics).forEach((key) => {
if (key === 'cpu_values' || key === 'memory_values') {
const metricValues = (metricsResponse.metrics[key])[0];
this.graphSpecificProperties[key].data = metricValues.values.map(metric => ({
time: new Date(metric[0] * 1000),
value: metric[1],
}));
}
});
}
updateState(prevState) {
const $statesContainer = $(prometheusStatesContainer);
$(prometheusParentGraphContainer).hide();
if (prevState) {
$(`${prevState}`, $statesContainer).addClass('hidden');
}
$(`${this.state}`, $statesContainer).removeClass('hidden');
$(prometheusStatesContainer).show();
}
}
export default PrometheusGraph;
import Vue from 'vue';
import VueResource from 'vue-resource';
Vue.use(VueResource);
export default class MonitoringService {
constructor(endpoint) {
this.graphs = Vue.resource(endpoint);
}
get() {
return this.graphs.get();
}
// eslint-disable-next-line class-methods-use-this
getDeploymentData(endpoint) {
return Vue.http.get(endpoint);
}
}
import _ from 'underscore';
class MonitoringStore {
constructor() {
this.groups = [];
this.deploymentData = [];
}
// eslint-disable-next-line class-methods-use-this
createArrayRows(metrics = []) {
const currentMetrics = metrics;
const availableMetrics = [];
let metricsRow = [];
let index = 1;
Object.keys(currentMetrics).forEach((key) => {
const metricValues = currentMetrics[key].queries[0].result[0].values;
if (metricValues != null) {
const literalMetrics = metricValues.map(metric => ({
time: new Date(metric[0] * 1000),
value: metric[1],
}));
currentMetrics[key].queries[0].result[0].values = literalMetrics;
metricsRow.push(currentMetrics[key]);
if (index % 2 === 0) {
availableMetrics.push(metricsRow);
metricsRow = [];
}
index = index += 1;
}
});
if (metricsRow.length > 0) {
availableMetrics.push(metricsRow);
}
return availableMetrics;
}
storeMetrics(groups = []) {
this.groups = groups.map((group) => {
const currentGroup = group;
currentGroup.metrics = _.chain(group.metrics).sortBy('weight').sortBy('title').value();
currentGroup.metrics = this.createArrayRows(currentGroup.metrics);
return currentGroup;
});
}
storeDeploymentData(deploymentData = []) {
this.deploymentData = deploymentData;
}
getMetricsCount() {
let metricsCount = 0;
this.groups.forEach((group) => {
group.metrics.forEach((metric) => {
metricsCount = metricsCount += metric.length;
});
});
return metricsCount;
}
}
export default MonitoringStore;
export default {
small: { // Covers both xs and sm screen sizes
margin: {
top: 40,
right: 40,
bottom: 50,
left: 40,
},
legends: {
width: 15,
height: 30,
},
backgroundLegend: {
width: 30,
height: 50,
},
axisLabelLineOffset: -20,
legendOffset: 52,
},
large: { // This covers both md and lg screen sizes
margin: {
top: 80,
right: 80,
bottom: 100,
left: 80,
},
legends: {
width: 20,
height: 35,
},
backgroundLegend: {
width: 30,
height: 150,
},
axisLabelLineOffset: 20,
legendOffset: 55,
},
ticks: 3,
};
...@@ -140,23 +140,6 @@ ...@@ -140,23 +140,6 @@
} }
} }
.prometheus-graph {
text {
fill: $gl-text-color;
stroke-width: 0;
}
.label-axis-text,
.text-metric-usage {
fill: $black;
font-weight: 500;
}
.legend-axis-text {
fill: $black;
}
}
.x-axis path, .x-axis path,
.y-axis path, .y-axis path,
.label-x-axis-line, .label-x-axis-line,
...@@ -205,6 +188,7 @@ ...@@ -205,6 +188,7 @@
.text-metric { .text-metric {
font-weight: 600; font-weight: 600;
font-size: 14px;
} }
.selected-metric-line { .selected-metric-line {
...@@ -214,20 +198,15 @@ ...@@ -214,20 +198,15 @@
.deployment-line { .deployment-line {
stroke: $black; stroke: $black;
stroke-width: 2; stroke-width: 1;
} }
.deploy-info-text { .deploy-info-text {
dominant-baseline: text-before-edge; dominant-baseline: text-before-edge;
} }
.text-metric-bold {
font-weight: 600;
}
.prometheus-state { .prometheus-state {
margin-top: 10px; margin-top: 10px;
display: none;
.state-button-section { .state-button-section {
margin-top: 10px; margin-top: 10px;
...@@ -242,3 +221,59 @@ ...@@ -242,3 +221,59 @@
width: 38px; width: 38px;
} }
} }
.prometheus-panel {
margin-top: 20px;
}
.prometheus-svg-container {
position: relative;
height: 0;
width: 100%;
padding: 0;
padding-bottom: 100%;
.text-metric-bold {
font-weight: 600;
}
}
.prometheus-svg-container > svg {
position: absolute;
height: 100%;
width: 100%;
left: 0;
top: 0;
text {
fill: $gl-text-color;
stroke-width: 0;
}
.label-axis-text,
.text-metric-usage {
fill: $black;
font-weight: 500;
font-size: 14px;
}
.legend-axis-text {
fill: $black;
}
.tick > text {
font-size: 14px;
}
@media (max-width: $screen-sm-max) {
.label-axis-text,
.text-metric-usage,
.legend-axis-text {
font-size: 8px;
}
.tick > text {
font-size: 8px;
}
}
}
- @no_container = true - @no_container = true
- page_title "Metrics for environment", @environment.name - page_title "Metrics for environment", @environment.name
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_d3') = webpack_bundle_tag 'common_vue'
= page_specific_javascript_bundle_tag('monitoring') = webpack_bundle_tag 'common_d3'
= webpack_bundle_tag 'monitoring'
= render "projects/pipelines/head" = render "projects/pipelines/head"
#js-metrics.prometheus-container{ class: container_class, data: { has_metrics: "#{@environment.has_metrics?}", deployment_endpoint: namespace_project_environment_deployments_path(@project.namespace, @project, @environment, format: :json) } } .prometheus-container{ class: container_class }
.top-area .top-area
.row .row
.col-sm-6 .col-sm-6
...@@ -13,68 +14,8 @@ ...@@ -13,68 +14,8 @@
Environment: Environment:
= link_to @environment.name, environment_path(@environment) = link_to @environment.name, environment_path(@environment)
.prometheus-state #prometheus-graphs{ data: { "settings-path": edit_namespace_project_service_path(@project.namespace, @project, 'prometheus'),
.js-getting-started.hidden "documentation-path": help_page_path('administration/monitoring/prometheus/index.md'),
.row "additional-metrics": additional_metrics_namespace_project_environment_path(@project.namespace, @project, @environment, format: :json),
.col-md-4.col-md-offset-4.state-svg "has-metrics": "#{@environment.has_metrics?}", deployment_endpoint: namespace_project_environment_deployments_path(@project.namespace, @project, @environment, format: :json) } }
= render "shared/empty_states/monitoring/getting_started.svg"
.row
.col-md-6.col-md-offset-3
%h4.text-center.state-title
Get started with performance monitoring
.row
.col-md-6.col-md-offset-3
.description-text.text-center.state-description
Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments.
= link_to help_page_path('administration/monitoring/prometheus/index.md') do
Learn more about performance monitoring
.row.state-button-section
.col-md-4.col-md-offset-4.text-center.state-button
= link_to edit_namespace_project_service_path(@project.namespace, @project, 'prometheus'), class: 'btn btn-success' do
Configure Prometheus
.js-loading.hidden
.row
.col-md-4.col-md-offset-4.state-svg
= render "shared/empty_states/monitoring/loading.svg"
.row
.col-md-6.col-md-offset-3
%h4.text-center.state-title
Waiting for performance data
.row
.col-md-6.col-md-offset-3
.description-text.text-center.state-description
Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available.
.row.state-button-section
.col-md-4.col-md-offset-4.text-center.state-button
= link_to help_page_path('administration/monitoring/prometheus/index.md'), class: 'btn btn-success' do
View documentation
.js-unable-to-connect.hidden
.row
.col-md-4.col-md-offset-4.state-svg
= render "shared/empty_states/monitoring/unable_to_connect.svg"
.row
.col-md-6.col-md-offset-3
%h4.text-center.state-title
Unable to connect to Prometheus server
.row
.col-md-6.col-md-offset-3
.description-text.text-center.state-description
Ensure connectivity is available from the GitLab server to the
= link_to edit_namespace_project_service_path(@project.namespace, @project, 'prometheus') do
Prometheus server
.row.state-button-section
.col-md-4.col-md-offset-4.text-center.state-button
= link_to help_page_path('administration/monitoring/prometheus/index.md'), class:'btn btn-success' do
View documentation
.prometheus-graphs
.row
.col-sm-12
%h4
CPU utilization
%svg.prometheus-graph{ 'graph-type' => 'cpu_values' }
.row
.col-sm-12
%h4
Memory usage
%svg.prometheus-graph{ 'graph-type' => 'memory_values' }
...@@ -163,6 +163,7 @@ var config = { ...@@ -163,6 +163,7 @@ var config = {
'issue_show', 'issue_show',
'job_details', 'job_details',
'merge_conflicts', 'merge_conflicts',
'monitoring',
'notebook_viewer', 'notebook_viewer',
'pdf_viewer', 'pdf_viewer',
'pipelines', 'pipelines',
......
...@@ -27,7 +27,7 @@ feature 'Environment > Metrics', :feature do ...@@ -27,7 +27,7 @@ feature 'Environment > Metrics', :feature do
scenario 'shows metrics' do scenario 'shows metrics' do
click_link('See metrics') click_link('See metrics')
expect(page).to have_css('svg.prometheus-graph') expect(page).to have_css('div#prometheus-graphs')
end end
end end
......
import d3 from 'd3';
import PrometheusGraph from '~/monitoring/prometheus_graph';
import Deployments from '~/monitoring/deployments';
import { prometheusMockData } from './prometheus_mock_data';
describe('Metrics deployments', () => {
const fixtureName = 'environments/metrics/metrics.html.raw';
let deployment;
let prometheusGraph;
const graphElement = () => document.querySelector('.prometheus-graph');
preloadFixtures(fixtureName);
beforeEach((done) => {
// Setup the view
loadFixtures(fixtureName);
d3.selectAll('.prometheus-graph')
.append('g')
.attr('class', 'graph-container');
prometheusGraph = new PrometheusGraph();
deployment = new Deployments(1000, 500);
spyOn(prometheusGraph, 'init');
spyOn($, 'ajax').and.callFake(() => {
const d = $.Deferred();
d.resolve({
deployments: [{
id: 1,
created_at: deployment.chartData[10].time,
sha: 'testing',
tag: false,
ref: {
name: 'testing',
},
}, {
id: 2,
created_at: deployment.chartData[15].time,
sha: '',
tag: true,
ref: {
name: 'tag',
},
}],
});
setTimeout(done);
return d.promise();
});
prometheusGraph.configureGraph();
prometheusGraph.transformData(prometheusMockData.metrics);
deployment.init(prometheusGraph.graphSpecificProperties.memory_values.data);
});
it('creates line on graph for deploment', () => {
expect(
graphElement().querySelectorAll('.deployment-line').length,
).toBe(2);
});
it('creates hidden deploy boxes', () => {
expect(
graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box').length,
).toBe(2);
});
it('hides the info boxes by default', () => {
expect(
graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box.hidden').length,
).toBe(2);
});
it('shows sha short code when tag is false', () => {
expect(
graphElement().querySelector('.deploy-info-1-cpu_values .js-deploy-info-box').textContent.trim(),
).toContain('testin');
});
it('shows ref name when tag is true', () => {
expect(
graphElement().querySelector('.deploy-info-2-cpu_values .js-deploy-info-box').textContent.trim(),
).toContain('tag');
});
it('shows info box when moving mouse over line', () => {
deployment.mouseOverDeployInfo(deployment.data[0].xPos, 'cpu_values');
expect(
graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box.hidden').length,
).toBe(1);
expect(
graphElement().querySelector('.deploy-info-1-cpu_values .js-deploy-info-box.hidden'),
).toBeNull();
});
it('hides previously visible info box when moving mouse away', () => {
deployment.mouseOverDeployInfo(500, 'cpu_values');
expect(
graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box.hidden').length,
).toBe(2);
expect(
graphElement().querySelector('.deploy-info-1-cpu_values .js-deploy-info-box.hidden'),
).not.toBeNull();
});
describe('refText', () => {
it('returns shortened SHA', () => {
expect(
Deployments.refText({
tag: false,
sha: '123456789',
}),
).toBe('123456');
});
it('returns tag name', () => {
expect(
Deployments.refText({
tag: true,
ref: 'v1.0',
}),
).toBe('v1.0');
});
});
});
This source diff could not be displayed because it is too large. You can view the blob instead.
import Vue from 'vue';
import _ from 'underscore';
import MonitoringColumn from '~/monitoring/components/monitoring_column.vue';
import MonitoringMixins from '~/monitoring/mixins/monitoring_mixins';
import eventHub from '~/monitoring/event_hub';
import { deploymentData, singleRowMetrics } from './mock_data';
const createComponent = (propsData) => {
const Component = Vue.extend(MonitoringColumn);
return new Component({
propsData,
}).$mount();
};
describe('MonitoringColumn', () => {
beforeEach(() => {
spyOn(MonitoringMixins.methods, 'formatDeployments').and.callFake(function fakeFormat() {
return {};
});
});
it('has a title', () => {
const component = createComponent({
columnData: singleRowMetrics[0],
classType: 'col-md-6',
updateAspectRatio: false,
deploymentData,
});
expect(component.$el.querySelector('.text-center').innerText.trim()).toBe(component.columnData.title);
});
it('creates a path for the line and area of the graph', (done) => {
const component = createComponent({
columnData: singleRowMetrics[0],
classType: 'col-md-6',
updateAspectRatio: false,
deploymentData,
});
Vue.nextTick(() => {
expect(component.area).toBeDefined();
expect(component.line).toBeDefined();
expect(typeof component.area).toEqual('string');
expect(typeof component.line).toEqual('string');
expect(_.isFunction(component.xScale)).toBe(true);
expect(_.isFunction(component.yScale)).toBe(true);
done();
});
});
describe('Computed props', () => {
it('axisTransform translates an element Y position depending of its height', () => {
const component = createComponent({
columnData: singleRowMetrics[0],
classType: 'col-md-6',
updateAspectRatio: false,
deploymentData,
});
const transformedHeight = `${component.graphHeight - 100}`;
expect(component.axisTransform.indexOf(transformedHeight))
.not.toEqual(-1);
});
it('outterViewBox gets a width and height property based on the DOM size of the element', () => {
const component = createComponent({
columnData: singleRowMetrics[0],
classType: 'col-md-6',
updateAspectRatio: false,
deploymentData,
});
const viewBoxArray = component.outterViewBox.split(' ');
expect(typeof component.outterViewBox).toEqual('string');
expect(viewBoxArray[2]).toEqual(component.graphWidth.toString());
expect(viewBoxArray[3]).toEqual(component.graphHeight.toString());
});
});
it('sends an event to the eventhub when it has finished resizing', (done) => {
const component = createComponent({
columnData: singleRowMetrics[0],
classType: 'col-md-6',
updateAspectRatio: false,
deploymentData,
});
spyOn(eventHub, '$emit');
component.updateAspectRatio = true;
Vue.nextTick(() => {
expect(eventHub.$emit).toHaveBeenCalled();
done();
});
});
});
import Vue from 'vue';
import MonitoringState from '~/monitoring/components/monitoring_deployment.vue';
import { deploymentData } from './mock_data';
const createComponent = (propsData) => {
const Component = Vue.extend(MonitoringState);
return new Component({
propsData,
}).$mount();
};
describe('MonitoringDeployment', () => {
const reducedDeploymentData = [deploymentData[0]];
reducedDeploymentData[0].ref = reducedDeploymentData[0].ref.name;
reducedDeploymentData[0].xPos = 10;
reducedDeploymentData[0].time = new Date(reducedDeploymentData[0].created_at);
describe('Methods', () => {
it('refText shows the ref when a tag is available', () => {
reducedDeploymentData[0].tag = '1.0';
const component = createComponent({
showDeployInfo: false,
deploymentData: reducedDeploymentData,
graphHeight: 300,
graphHeightOffset: 120,
});
expect(
component.refText(reducedDeploymentData[0]),
).toEqual(reducedDeploymentData[0].ref);
});
it('refText shows the sha when no tag is available', () => {
reducedDeploymentData[0].tag = null;
const component = createComponent({
showDeployInfo: false,
deploymentData: reducedDeploymentData,
graphHeight: 300,
graphHeightOffset: 120,
});
expect(
component.refText(reducedDeploymentData[0]),
).toContain('f5bcd1');
});
it('nameDeploymentClass creates a class with the prefix deploy-info-', () => {
const component = createComponent({
showDeployInfo: false,
deploymentData: reducedDeploymentData,
graphHeight: 300,
graphHeightOffset: 120,
});
expect(
component.nameDeploymentClass(reducedDeploymentData[0]),
).toContain('deploy-info');
});
it('transformDeploymentGroup translates an available deployment', () => {
const component = createComponent({
showDeployInfo: false,
deploymentData: reducedDeploymentData,
graphHeight: 300,
graphHeightOffset: 120,
});
expect(
component.transformDeploymentGroup(reducedDeploymentData[0]),
).toContain('translate(11, 20)');
});
it('hides the deployment flag', () => {
reducedDeploymentData[0].showDeploymentFlag = false;
const component = createComponent({
showDeployInfo: true,
deploymentData: reducedDeploymentData,
graphHeight: 300,
graphHeightOffset: 120,
});
expect(component.$el.querySelector('.js-deploy-info-box')).toBeNull();
});
it('shows the deployment flag', () => {
reducedDeploymentData[0].showDeploymentFlag = true;
const component = createComponent({
showDeployInfo: true,
deploymentData: reducedDeploymentData,
graphHeight: 300,
graphHeightOffset: 120,
});
expect(
component.$el.querySelector('.js-deploy-info-box').style.display,
).not.toEqual('display: none;');
});
it('shows the refText inside a text element with the deploy-info-text class', () => {
reducedDeploymentData[0].showDeploymentFlag = true;
const component = createComponent({
showDeployInfo: true,
deploymentData: reducedDeploymentData,
graphHeight: 300,
graphHeightOffset: 120,
});
expect(
component.$el.querySelector('.deploy-info-text').firstChild.nodeValue.trim(),
).toEqual(component.refText(reducedDeploymentData[0]));
});
it('should contain a hidden gradient', () => {
const component = createComponent({
showDeployInfo: true,
deploymentData: reducedDeploymentData,
graphHeight: 300,
graphHeightOffset: 120,
});
expect(component.$el.querySelector('#shadow-gradient')).not.toBeNull();
});
describe('Computed props', () => {
it('calculatedHeight', () => {
const component = createComponent({
showDeployInfo: true,
deploymentData: reducedDeploymentData,
graphHeight: 300,
graphHeightOffset: 120,
});
expect(component.calculatedHeight).toEqual(180);
});
});
});
});
import Vue from 'vue';
import MonitoringFlag from '~/monitoring/components/monitoring_flag.vue';
const createComponent = (propsData) => {
const Component = Vue.extend(MonitoringFlag);
return new Component({
propsData,
}).$mount();
};
function getCoordinate(component, selector, coordinate) {
const coordinateVal = component.$el.querySelector(selector).getAttribute(coordinate);
return parseInt(coordinateVal, 10);
}
describe('MonitoringFlag', () => {
it('has a line and a circle located at the currentXCoordinate and currentYCoordinate', () => {
const component = createComponent({
currentXCoordinate: 200,
currentYCoordinate: 100,
currentFlagPosition: 100,
currentData: {
time: new Date('2017-06-04T18:17:33.501Z'),
value: '1.49609375',
},
graphHeight: 300,
graphHeightOffset: 120,
});
expect(getCoordinate(component, '.selected-metric-line', 'x1'))
.toEqual(component.currentXCoordinate);
expect(getCoordinate(component, '.selected-metric-line', 'x2'))
.toEqual(component.currentXCoordinate);
expect(getCoordinate(component, '.circle-metric', 'cx'))
.toEqual(component.currentXCoordinate);
expect(getCoordinate(component, '.circle-metric', 'cy'))
.toEqual(component.currentYCoordinate);
});
it('has a SVG with the class rect-text-metric at the currentFlagPosition', () => {
const component = createComponent({
currentXCoordinate: 200,
currentYCoordinate: 100,
currentFlagPosition: 100,
currentData: {
time: new Date('2017-06-04T18:17:33.501Z'),
value: '1.49609375',
},
graphHeight: 300,
graphHeightOffset: 120,
});
const svg = component.$el.querySelector('.rect-text-metric');
expect(svg.tagName).toEqual('svg');
expect(parseInt(svg.getAttribute('x'), 10)).toEqual(component.currentFlagPosition);
});
describe('Computed props', () => {
it('calculatedHeight', () => {
const component = createComponent({
currentXCoordinate: 200,
currentYCoordinate: 100,
currentFlagPosition: 100,
currentData: {
time: new Date('2017-06-04T18:17:33.501Z'),
value: '1.49609375',
},
graphHeight: 300,
graphHeightOffset: 120,
});
expect(component.calculatedHeight).toEqual(180);
});
});
});
import Vue from 'vue';
import MonitoringLegends from '~/monitoring/components/monitoring_legends.vue';
import measurements from '~/monitoring/utils/measurements';
const createComponent = (propsData) => {
const Component = Vue.extend(MonitoringLegends);
return new Component({
propsData,
}).$mount();
};
function getTextFromNode(component, selector) {
return component.$el.querySelector(selector).firstChild.nodeValue.trim();
}
describe('MonitoringLegends', () => {
describe('Computed props', () => {
it('textTransform', () => {
const component = createComponent({
graphWidth: 500,
graphHeight: 300,
margin: measurements.large.margin,
measurements: measurements.large,
areaColorRgb: '#f0f0f0',
legendTitle: 'Title',
yAxisLabel: 'Values',
metricUsage: 'Value',
});
expect(component.textTransform).toContain('translate(15, 120) rotate(-90)');
});
it('xPosition', () => {
const component = createComponent({
graphWidth: 500,
graphHeight: 300,
margin: measurements.large.margin,
measurements: measurements.large,
areaColorRgb: '#f0f0f0',
legendTitle: 'Title',
yAxisLabel: 'Values',
metricUsage: 'Value',
});
expect(component.xPosition).toEqual(180);
});
it('yPosition', () => {
const component = createComponent({
graphWidth: 500,
graphHeight: 300,
margin: measurements.large.margin,
measurements: measurements.large,
areaColorRgb: '#f0f0f0',
legendTitle: 'Title',
yAxisLabel: 'Values',
metricUsage: 'Value',
});
expect(component.yPosition).toEqual(240);
});
it('rectTransform', () => {
const component = createComponent({
graphWidth: 500,
graphHeight: 300,
margin: measurements.large.margin,
measurements: measurements.large,
areaColorRgb: '#f0f0f0',
legendTitle: 'Title',
yAxisLabel: 'Values',
metricUsage: 'Value',
});
expect(component.rectTransform).toContain('translate(0, 120) rotate(-90)');
});
});
it('has 2 rect-axis-text rect svg elements', () => {
const component = createComponent({
graphWidth: 500,
graphHeight: 300,
margin: measurements.large.margin,
measurements: measurements.large,
areaColorRgb: '#f0f0f0',
legendTitle: 'Title',
yAxisLabel: 'Values',
metricUsage: 'Value',
});
expect(component.$el.querySelectorAll('.rect-axis-text').length).toEqual(2);
});
it('contains text to signal the usage, title and time', () => {
const component = createComponent({
graphWidth: 500,
graphHeight: 300,
margin: measurements.large.margin,
measurements: measurements.large,
areaColorRgb: '#f0f0f0',
legendTitle: 'Title',
yAxisLabel: 'Values',
metricUsage: 'Value',
});
expect(getTextFromNode(component, '.text-metric-title')).toEqual(component.legendTitle);
expect(getTextFromNode(component, '.text-metric-usage')).toEqual(component.metricUsage);
expect(getTextFromNode(component, '.label-axis-text')).toEqual(component.yAxisLabel);
});
});
import Vue from 'vue';
import MonitoringRow from '~/monitoring/components/monitoring_row.vue';
import { deploymentData, singleRowMetrics } from './mock_data';
const createComponent = (propsData) => {
const Component = Vue.extend(MonitoringRow);
return new Component({
propsData,
}).$mount();
};
describe('MonitoringRow', () => {
describe('Computed props', () => {
it('bootstrapClass is set to col-md-6 when rowData is higher/equal to 2', () => {
const component = createComponent({
rowData: singleRowMetrics,
updateAspectRatio: false,
deploymentData,
});
expect(component.bootstrapClass).toEqual('col-md-6');
});
it('bootstrapClass is set to col-md-12 when rowData is lower than 2', () => {
const component = createComponent({
rowData: [singleRowMetrics[0]],
updateAspectRatio: false,
deploymentData,
});
expect(component.bootstrapClass).toEqual('col-md-12');
});
});
it('has one column', () => {
const component = createComponent({
rowData: singleRowMetrics,
updateAspectRatio: false,
deploymentData,
});
expect(component.$el.querySelectorAll('.prometheus-svg-container').length)
.toEqual(component.rowData.length);
});
it('has two columns', () => {
const component = createComponent({
rowData: singleRowMetrics,
updateAspectRatio: false,
deploymentData,
});
expect(component.$el.querySelectorAll('.col-md-6').length)
.toEqual(component.rowData.length);
});
});
import Vue from 'vue';
import Monitoring from '~/monitoring/components/monitoring.vue';
import { MonitorMockInterceptor } from './mock_data';
describe('Monitoring', () => {
const fixtureName = 'environments/metrics/metrics.html.raw';
let MonitoringComponent;
let component;
preloadFixtures(fixtureName);
beforeEach(() => {
loadFixtures(fixtureName);
MonitoringComponent = Vue.extend(Monitoring);
});
describe('no metrics are available yet', () => {
it('shows a getting started empty state when no metrics are present', () => {
component = new MonitoringComponent({
el: document.querySelector('#prometheus-graphs'),
});
component.$mount();
expect(component.$el.querySelector('#prometheus-graphs')).toBe(null);
expect(component.state).toEqual('gettingStarted');
});
});
describe('requests information to the server', () => {
beforeEach(() => {
document.querySelector('#prometheus-graphs').setAttribute('data-has-metrics', 'true');
Vue.http.interceptors.push(MonitorMockInterceptor);
});
afterEach(() => {
Vue.http.interceptors = _.without(Vue.http.interceptors, MonitorMockInterceptor);
});
it('shows up a loading state', (done) => {
component = new MonitoringComponent({
el: document.querySelector('#prometheus-graphs'),
});
component.$mount();
Vue.nextTick(() => {
expect(component.state).toEqual('loading');
done();
});
});
});
});
import Vue from 'vue';
import MonitoringState from '~/monitoring/components/monitoring_state.vue';
import { statePaths } from './mock_data';
const createComponent = (propsData) => {
const Component = Vue.extend(MonitoringState);
return new Component({
propsData,
}).$mount();
};
function getTextFromNode(component, selector) {
return component.$el.querySelector(selector).firstChild.nodeValue.trim();
}
describe('MonitoringState', () => {
describe('Computed props', () => {
it('currentState', () => {
const component = createComponent({
selectedState: 'gettingStarted',
settingsPath: statePaths.settingsPath,
documentationPath: statePaths.documentationPath,
});
expect(component.currentState).toBe(component.states.gettingStarted);
});
it('buttonPath returns settings path for the state "gettingStarted"', () => {
const component = createComponent({
selectedState: 'gettingStarted',
settingsPath: statePaths.settingsPath,
documentationPath: statePaths.documentationPath,
});
expect(component.buttonPath).toEqual(statePaths.settingsPath);
expect(component.buttonPath).not.toEqual(statePaths.documentationPath);
});
it('buttonPath returns documentation path for any of the other states', () => {
const component = createComponent({
selectedState: 'loading',
settingsPath: statePaths.settingsPath,
documentationPath: statePaths.documentationPath,
});
expect(component.buttonPath).toEqual(statePaths.documentationPath);
expect(component.buttonPath).not.toEqual(statePaths.settingsPath);
});
it('showButtonDescription returns a description with a link for the unableToConnect state', () => {
const component = createComponent({
selectedState: 'unableToConnect',
settingsPath: statePaths.settingsPath,
documentationPath: statePaths.documentationPath,
});
expect(component.showButtonDescription).toEqual(true);
});
it('showButtonDescription returns the description without a link for any other state', () => {
const component = createComponent({
selectedState: 'loading',
settingsPath: statePaths.settingsPath,
documentationPath: statePaths.documentationPath,
});
expect(component.showButtonDescription).toEqual(false);
});
});
it('should show the gettingStarted state', () => {
const component = createComponent({
selectedState: 'gettingStarted',
settingsPath: statePaths.settingsPath,
documentationPath: statePaths.documentationPath,
});
expect(component.$el.querySelector('svg')).toBeDefined();
expect(getTextFromNode(component, '.state-title')).toEqual(component.states.gettingStarted.title);
expect(getTextFromNode(component, '.state-description')).toEqual(component.states.gettingStarted.description);
expect(getTextFromNode(component, '.btn-success')).toEqual(component.states.gettingStarted.buttonText);
});
it('should show the loading state', () => {
const component = createComponent({
selectedState: 'loading',
settingsPath: statePaths.settingsPath,
documentationPath: statePaths.documentationPath,
});
expect(component.$el.querySelector('svg')).toBeDefined();
expect(getTextFromNode(component, '.state-title')).toEqual(component.states.loading.title);
expect(getTextFromNode(component, '.state-description')).toEqual(component.states.loading.description);
expect(getTextFromNode(component, '.btn-success')).toEqual(component.states.loading.buttonText);
});
it('should show the unableToConnect state', () => {
const component = createComponent({
selectedState: 'unableToConnect',
settingsPath: statePaths.settingsPath,
documentationPath: statePaths.documentationPath,
});
expect(component.$el.querySelector('svg')).toBeDefined();
expect(getTextFromNode(component, '.state-title')).toEqual(component.states.unableToConnect.title);
expect(component.$el.querySelector('.state-description a')).toBeDefined();
expect(getTextFromNode(component, '.btn-success')).toEqual(component.states.unableToConnect.buttonText);
});
});
import MonitoringStore from '~/monitoring/stores/monitoring_store';
import MonitoringMock, { deploymentData } from './mock_data';
describe('MonitoringStore', () => {
this.store = new MonitoringStore();
this.store.storeMetrics(MonitoringMock.data);
it('contains one group that contains two queries sorted by priority in one row', () => {
expect(this.store.groups).toBeDefined();
expect(this.store.groups.length).toEqual(1);
expect(this.store.groups[0].metrics.length).toEqual(1);
});
it('gets the metrics count for every group', () => {
expect(this.store.getMetricsCount()).toEqual(2);
});
it('contains deployment data', () => {
this.store.storeDeploymentData(deploymentData);
expect(this.store.deploymentData).toBeDefined();
expect(this.store.deploymentData.length).toEqual(3);
expect(typeof this.store.deploymentData[0]).toEqual('object');
});
});
import 'jquery';
import PrometheusGraph from '~/monitoring/prometheus_graph';
import { prometheusMockData } from './prometheus_mock_data';
describe('PrometheusGraph', () => {
const fixtureName = 'environments/metrics/metrics.html.raw';
const prometheusGraphContainer = '.prometheus-graph';
const prometheusGraphContents = `${prometheusGraphContainer}[graph-type=cpu_values]`;
preloadFixtures(fixtureName);
beforeEach(() => {
loadFixtures(fixtureName);
$('.prometheus-container').data('has-metrics', 'true');
this.prometheusGraph = new PrometheusGraph();
const self = this;
const fakeInit = (metricsResponse) => {
self.prometheusGraph.transformData(metricsResponse);
self.prometheusGraph.createGraph();
};
spyOn(this.prometheusGraph, 'init').and.callFake(fakeInit);
});
it('initializes graph properties', () => {
// Test for the measurements
expect(this.prometheusGraph.margin).toBeDefined();
expect(this.prometheusGraph.marginLabelContainer).toBeDefined();
expect(this.prometheusGraph.originalWidth).toBeDefined();
expect(this.prometheusGraph.originalHeight).toBeDefined();
expect(this.prometheusGraph.height).toBeDefined();
expect(this.prometheusGraph.width).toBeDefined();
expect(this.prometheusGraph.backOffRequestCounter).toBeDefined();
// Test for the graph properties (colors, radius, etc.)
expect(this.prometheusGraph.graphSpecificProperties).toBeDefined();
expect(this.prometheusGraph.commonGraphProperties).toBeDefined();
});
it('transforms the data', () => {
this.prometheusGraph.init(prometheusMockData.metrics);
Object.keys(this.prometheusGraph.graphSpecificProperties, (key) => {
const graphProps = this.prometheusGraph.graphSpecificProperties[key];
expect(graphProps.data).toBeDefined();
expect(graphProps.data.length).toBe(121);
});
});
it('creates two graphs', () => {
this.prometheusGraph.init(prometheusMockData.metrics);
expect($(prometheusGraphContainer).length).toBe(2);
});
describe('Graph contents', () => {
beforeEach(() => {
this.prometheusGraph.init(prometheusMockData.metrics);
});
it('has axis, an area, a line and a overlay', () => {
const $graphContainer = $(prometheusGraphContents).find('.x-axis').parent();
expect($graphContainer.find('.x-axis')).toBeDefined();
expect($graphContainer.find('.y-axis')).toBeDefined();
expect($graphContainer.find('.prometheus-graph-overlay')).toBeDefined();
expect($graphContainer.find('.metric-line')).toBeDefined();
expect($graphContainer.find('.metric-area')).toBeDefined();
});
it('has legends, labels and an extra axis that labels the metrics', () => {
const $prometheusGraphContents = $(prometheusGraphContents);
const $axisLabelContainer = $(prometheusGraphContents).find('.label-x-axis-line').parent();
expect($prometheusGraphContents.find('.label-x-axis-line')).toBeDefined();
expect($prometheusGraphContents.find('.label-y-axis-line')).toBeDefined();
expect($prometheusGraphContents.find('.label-axis-text')).toBeDefined();
expect($prometheusGraphContents.find('.rect-axis-text')).toBeDefined();
expect($axisLabelContainer.find('rect').length).toBe(3);
expect($axisLabelContainer.find('text').length).toBe(4);
});
});
});
describe('PrometheusGraphs UX states', () => {
const fixtureName = 'environments/metrics/metrics.html.raw';
preloadFixtures(fixtureName);
beforeEach(() => {
loadFixtures(fixtureName);
this.prometheusGraph = new PrometheusGraph();
});
it('shows a specified state', () => {
this.prometheusGraph.state = '.js-getting-started';
this.prometheusGraph.updateState();
const $state = $('.js-getting-started');
expect($state).toBeDefined();
expect($('.state-title', $state)).toBeDefined();
expect($('.state-svg', $state)).toBeDefined();
expect($('.state-description', $state)).toBeDefined();
expect($('.state-button', $state)).toBeDefined();
});
});
/* eslint-disable import/prefer-default-export*/
export const prometheusMockData = {
status: 200,
metrics: {
success: true,
metrics: {
memory_values: [
{
metric: {
},
values: [
[
1488462917.256,
'10.12890625',
],
[
1488462977.256,
'10.140625',
],
[
1488463037.256,
'10.140625',
],
[
1488463097.256,
'10.14453125',
],
[
1488463157.256,
'10.1484375',
],
[
1488463217.256,
'10.15625',
],
[
1488463277.256,
'10.15625',
],
[
1488463337.256,
'10.15625',
],
[
1488463397.256,
'10.1640625',
],
[
1488463457.256,
'10.171875',
],
[
1488463517.256,
'10.171875',
],
[
1488463577.256,
'10.171875',
],
[
1488463637.256,
'10.18359375',
],
[
1488463697.256,
'10.1953125',
],
[
1488463757.256,
'10.203125',
],
[
1488463817.256,
'10.20703125',
],
[
1488463877.256,
'10.20703125',
],
[
1488463937.256,
'10.20703125',
],
[
1488463997.256,
'10.20703125',
],
[
1488464057.256,
'10.2109375',
],
[
1488464117.256,
'10.2109375',
],
[
1488464177.256,
'10.2109375',
],
[
1488464237.256,
'10.2109375',
],
[
1488464297.256,
'10.21484375',
],
[
1488464357.256,
'10.22265625',
],
[
1488464417.256,
'10.22265625',
],
[
1488464477.256,
'10.2265625',
],
[
1488464537.256,
'10.23046875',
],
[
1488464597.256,
'10.23046875',
],
[
1488464657.256,
'10.234375',
],
[
1488464717.256,
'10.234375',
],
[
1488464777.256,
'10.234375',
],
[
1488464837.256,
'10.234375',
],
[
1488464897.256,
'10.234375',
],
[
1488464957.256,
'10.234375',
],
[
1488465017.256,
'10.23828125',
],
[
1488465077.256,
'10.23828125',
],
[
1488465137.256,
'10.2421875',
],
[
1488465197.256,
'10.2421875',
],
[
1488465257.256,
'10.2421875',
],
[
1488465317.256,
'10.2421875',
],
[
1488465377.256,
'10.2421875',
],
[
1488465437.256,
'10.2421875',
],
[
1488465497.256,
'10.2421875',
],
[
1488465557.256,
'10.2421875',
],
[
1488465617.256,
'10.2421875',
],
[
1488465677.256,
'10.2421875',
],
[
1488465737.256,
'10.2421875',
],
[
1488465797.256,
'10.24609375',
],
[
1488465857.256,
'10.25',
],
[
1488465917.256,
'10.25390625',
],
[
1488465977.256,
'9.98828125',
],
[
1488466037.256,
'9.9921875',
],
[
1488466097.256,
'9.9921875',
],
[
1488466157.256,
'9.99609375',
],
[
1488466217.256,
'10',
],
[
1488466277.256,
'10.00390625',
],
[
1488466337.256,
'10.0078125',
],
[
1488466397.256,
'10.01171875',
],
[
1488466457.256,
'10.0234375',
],
[
1488466517.256,
'10.02734375',
],
[
1488466577.256,
'10.02734375',
],
[
1488466637.256,
'10.03125',
],
[
1488466697.256,
'10.03125',
],
[
1488466757.256,
'10.03125',
],
[
1488466817.256,
'10.03125',
],
[
1488466877.256,
'10.03125',
],
[
1488466937.256,
'10.03125',
],
[
1488466997.256,
'10.03125',
],
[
1488467057.256,
'10.0390625',
],
[
1488467117.256,
'10.0390625',
],
[
1488467177.256,
'10.04296875',
],
[
1488467237.256,
'10.05078125',
],
[
1488467297.256,
'10.05859375',
],
[
1488467357.256,
'10.06640625',
],
[
1488467417.256,
'10.06640625',
],
[
1488467477.256,
'10.0703125',
],
[
1488467537.256,
'10.07421875',
],
[
1488467597.256,
'10.0859375',
],
[
1488467657.256,
'10.0859375',
],
[
1488467717.256,
'10.09765625',
],
[
1488467777.256,
'10.1015625',
],
[
1488467837.256,
'10.10546875',
],
[
1488467897.256,
'10.10546875',
],
[
1488467957.256,
'10.125',
],
[
1488468017.256,
'10.13671875',
],
[
1488468077.256,
'10.1484375',
],
[
1488468137.256,
'10.15625',
],
[
1488468197.256,
'10.16796875',
],
[
1488468257.256,
'10.171875',
],
[
1488468317.256,
'10.171875',
],
[
1488468377.256,
'10.171875',
],
[
1488468437.256,
'10.171875',
],
[
1488468497.256,
'10.171875',
],
[
1488468557.256,
'10.171875',
],
[
1488468617.256,
'10.171875',
],
[
1488468677.256,
'10.17578125',
],
[
1488468737.256,
'10.17578125',
],
[
1488468797.256,
'10.265625',
],
[
1488468857.256,
'10.19921875',
],
[
1488468917.256,
'10.19921875',
],
[
1488468977.256,
'10.19921875',
],
[
1488469037.256,
'10.19921875',
],
[
1488469097.256,
'10.19921875',
],
[
1488469157.256,
'10.203125',
],
[
1488469217.256,
'10.43359375',
],
[
1488469277.256,
'10.20703125',
],
[
1488469337.256,
'10.2109375',
],
[
1488469397.256,
'10.22265625',
],
[
1488469457.256,
'10.21484375',
],
[
1488469517.256,
'10.21484375',
],
[
1488469577.256,
'10.21484375',
],
[
1488469637.256,
'10.22265625',
],
[
1488469697.256,
'10.234375',
],
[
1488469757.256,
'10.234375',
],
[
1488469817.256,
'10.234375',
],
[
1488469877.256,
'10.2421875',
],
[
1488469937.256,
'10.25',
],
[
1488469997.256,
'10.25390625',
],
[
1488470057.256,
'10.26171875',
],
[
1488470117.256,
'10.2734375',
],
],
},
],
memory_current: [
{
metric: {
},
value: [
1488470117.737,
'10.2734375',
],
},
],
cpu_values: [
{
metric: {
},
values: [
[
1488462918.15,
'0.0002996458625058103',
],
[
1488462978.15,
'0.0002652382333333314',
],
[
1488463038.15,
'0.0003485461333333421',
],
[
1488463098.15,
'0.0003420421999999886',
],
[
1488463158.15,
'0.00023107150000001297',
],
[
1488463218.15,
'0.00030463981666664826',
],
[
1488463278.15,
'0.0002477177833333677',
],
[
1488463338.15,
'0.00026936656666665115',
],
[
1488463398.15,
'0.000406264750000022',
],
[
1488463458.15,
'0.00029592802026561453',
],
[
1488463518.15,
'0.00023426999683316343',
],
[
1488463578.15,
'0.0003057080666666915',
],
[
1488463638.15,
'0.0003408470500000149',
],
[
1488463698.15,
'0.00025497336666665166',
],
[
1488463758.15,
'0.0003009282833333534',
],
[
1488463818.15,
'0.0003119383499999924',
],
[
1488463878.15,
'0.00028719019999998705',
],
[
1488463938.15,
'0.000327864749999988',
],
[
1488463998.15,
'0.0002514917333333422',
],
[
1488464058.15,
'0.0003614651166666742',
],
[
1488464118.15,
'0.0003221668000000122',
],
[
1488464178.15,
'0.00023323083333330884',
],
[
1488464238.15,
'0.00028531499475009274',
],
[
1488464298.15,
'0.0002627695294921391',
],
[
1488464358.15,
'0.00027145463333333453',
],
[
1488464418.15,
'0.00025669488333335266',
],
[
1488464478.15,
'0.00022307761666665965',
],
[
1488464538.15,
'0.0003307265833333517',
],
[
1488464598.15,
'0.0002817050666666709',
],
[
1488464658.15,
'0.00022357458333332285',
],
[
1488464718.15,
'0.00032648590000000275',
],
[
1488464778.15,
'0.00028410750000000816',
],
[
1488464838.15,
'0.0003038076999999954',
],
[
1488464898.15,
'0.00037568226666667335',
],
[
1488464958.15,
'0.00020160354999999202',
],
[
1488465018.15,
'0.0003229403333333399',
],
[
1488465078.15,
'0.00033516069999999236',
],
[
1488465138.15,
'0.0003365978333333371',
],
[
1488465198.15,
'0.00020262178333331585',
],
[
1488465258.15,
'0.00040567498333331876',
],
[
1488465318.15,
'0.00029114155000001436',
],
[
1488465378.15,
'0.0002498841000000122',
],
[
1488465438.15,
'0.00027296763333331715',
],
[
1488465498.15,
'0.0002958794000000135',
],
[
1488465558.15,
'0.0002922354666666867',
],
[
1488465618.15,
'0.00034186624999999653',
],
[
1488465678.15,
'0.0003397984166666627',
],
[
1488465738.15,
'0.0002658284166666469',
],
[
1488465798.15,
'0.00026221139999999346',
],
[
1488465858.15,
'0.00029467960000001034',
],
[
1488465918.15,
'0.0002634141333333358',
],
[
1488465978.15,
'0.0003202958333333209',
],
[
1488466038.15,
'0.00037890760000000394',
],
[
1488466098.15,
'0.00023453356666666518',
],
[
1488466158.15,
'0.0002866827333333433',
],
[
1488466218.15,
'0.0003335935499999998',
],
[
1488466278.15,
'0.00022787131666666125',
],
[
1488466338.15,
'0.00033821938333333064',
],
[
1488466398.15,
'0.00029233375000001043',
],
[
1488466458.15,
'0.00026562758333333514',
],
[
1488466518.15,
'0.0003142600999999819',
],
[
1488466578.15,
'0.00027392178333333444',
],
[
1488466638.15,
'0.00028178598333334173',
],
[
1488466698.15,
'0.0002463400666666911',
],
[
1488466758.15,
'0.00040234373333332125',
],
[
1488466818.15,
'0.00023677453333332822',
],
[
1488466878.15,
'0.00030852703333333523',
],
[
1488466938.15,
'0.0003582272833333455',
],
[
1488466998.15,
'0.0002176380833332973',
],
[
1488467058.15,
'0.00026180203333335447',
],
[
1488467118.15,
'0.00027862966666667436',
],
[
1488467178.15,
'0.0002769731166666567',
],
[
1488467238.15,
'0.0002832899166666477',
],
[
1488467298.15,
'0.0003446533500000311',
],
[
1488467358.15,
'0.0002691345999999761',
],
[
1488467418.15,
'0.000284919933333357',
],
[
1488467478.15,
'0.0002396026166666528',
],
[
1488467538.15,
'0.00035625295000002075',
],
[
1488467598.15,
'0.00036759816666664946',
],
[
1488467658.15,
'0.00030326608333333855',
],
[
1488467718.15,
'0.00023584972418043393',
],
[
1488467778.15,
'0.00025744508892115107',
],
[
1488467838.15,
'0.00036737541666663395',
],
[
1488467898.15,
'0.00034325741666666094',
],
[
1488467958.15,
'0.00026390046666667407',
],
[
1488468018.15,
'0.0003302534500000102',
],
[
1488468078.15,
'0.00035243794999999527',
],
[
1488468138.15,
'0.00020149738333333407',
],
[
1488468198.15,
'0.0003183469666666679',
],
[
1488468258.15,
'0.0003835329166666845',
],
[
1488468318.15,
'0.0002485075333333124',
],
[
1488468378.15,
'0.0003011457166666768',
],
[
1488468438.15,
'0.00032242785497684965',
],
[
1488468498.15,
'0.0002659713747457531',
],
[
1488468558.15,
'0.0003476860333333202',
],
[
1488468618.15,
'0.00028336403333334794',
],
[
1488468678.15,
'0.00017132354999998728',
],
[
1488468738.15,
'0.0003001915833333276',
],
[
1488468798.15,
'0.0003025715666666725',
],
[
1488468858.15,
'0.0003012370166666815',
],
[
1488468918.15,
'0.00030203619999997025',
],
[
1488468978.15,
'0.0002804355000000314',
],
[
1488469038.15,
'0.00033194884999998564',
],
[
1488469098.15,
'0.00025201496666665455',
],
[
1488469158.15,
'0.0002777531500000189',
],
[
1488469218.15,
'0.0003314885833333392',
],
[
1488469278.15,
'0.0002234891422095589',
],
[
1488469338.15,
'0.000349117355867791',
],
[
1488469398.15,
'0.0004036731333333303',
],
[
1488469458.15,
'0.00024553911666667835',
],
[
1488469518.15,
'0.0003056456833333184',
],
[
1488469578.15,
'0.0002618737166666681',
],
[
1488469638.15,
'0.00022972643333331414',
],
[
1488469698.15,
'0.0003713522500000307',
],
[
1488469758.15,
'0.00018322576666666515',
],
[
1488469818.15,
'0.00034534762753952466',
],
[
1488469878.15,
'0.00028200510008501677',
],
[
1488469938.15,
'0.0002773708499999768',
],
[
1488469998.15,
'0.00027547160000001013',
],
[
1488470058.15,
'0.00031713610000000023',
],
[
1488470118.15,
'0.00035276853333332525',
],
],
},
],
cpu_current: [
{
metric: {
},
value: [
1488470118.566,
'0.00035276853333332525',
],
},
],
last_update: '2017-03-02T15:55:18.981Z',
},
},
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment