Commit 657fea86 authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Add summary statistics prometheus dashboard

parent 9e3cdc02
...@@ -3,6 +3,7 @@ import { scaleLinear, scaleTime } from 'd3-scale'; ...@@ -3,6 +3,7 @@ import { scaleLinear, scaleTime } from 'd3-scale';
import { axisLeft, axisBottom } from 'd3-axis'; import { axisLeft, axisBottom } from 'd3-axis';
import { max, extent } from 'd3-array'; import { max, extent } from 'd3-array';
import { select } from 'd3-selection'; import { select } from 'd3-selection';
import GraphAxis from './graph/axis.vue';
import GraphLegend from './graph/legend.vue'; import GraphLegend from './graph/legend.vue';
import GraphFlag from './graph/flag.vue'; import GraphFlag from './graph/flag.vue';
import GraphDeployment from './graph/deployment.vue'; import GraphDeployment from './graph/deployment.vue';
...@@ -18,10 +19,11 @@ const d3 = { scaleLinear, scaleTime, axisLeft, axisBottom, max, extent, select } ...@@ -18,10 +19,11 @@ const d3 = { scaleLinear, scaleTime, axisLeft, axisBottom, max, extent, select }
export default { export default {
components: { components: {
GraphLegend, GraphAxis,
GraphFlag, GraphFlag,
GraphDeployment, GraphDeployment,
GraphPath, GraphPath,
GraphLegend,
}, },
mixins: [MonitoringMixin], mixins: [MonitoringMixin],
props: { props: {
...@@ -138,7 +140,7 @@ export default { ...@@ -138,7 +140,7 @@ export default {
this.legendTitle = query.label || 'Average'; this.legendTitle = query.label || 'Average';
this.graphWidth = this.$refs.baseSvg.clientWidth - this.margin.left - this.margin.right; this.graphWidth = this.$refs.baseSvg.clientWidth - this.margin.left - this.margin.right;
this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom; this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom;
this.baseGraphHeight = this.graphHeight; this.baseGraphHeight = this.graphHeight - 50;
this.baseGraphWidth = this.graphWidth; this.baseGraphWidth = this.graphWidth;
// pixel offsets inside the svg and outside are not 1:1 // pixel offsets inside the svg and outside are not 1:1
...@@ -177,14 +179,10 @@ export default { ...@@ -177,14 +179,10 @@ export default {
this.graphHeightOffset, this.graphHeightOffset,
); );
if (!this.showLegend) { const axisXScale = d3.scaleTime()
this.baseGraphHeight -= 50; .range([0, this.graphWidth - 70]);
} else if (this.timeSeries.length > 3) { const axisYScale = d3.scaleLinear()
this.baseGraphHeight = this.baseGraphHeight += (this.timeSeries.length - 3) * 20; .range([this.graphHeight - this.graphHeightOffset, 0]);
}
const axisXScale = d3.scaleTime().range([0, this.graphWidth - 70]);
const axisYScale = d3.scaleLinear().range([this.graphHeight - this.graphHeightOffset, 0]);
const allValues = this.timeSeries.reduce((all, { values }) => all.concat(values), []); const allValues = this.timeSeries.reduce((all, { values }) => all.concat(values), []);
axisXScale.domain(d3.extent(allValues, d => d.time)); axisXScale.domain(d3.extent(allValues, d => d.time));
...@@ -251,17 +249,12 @@ export default { ...@@ -251,17 +249,12 @@ export default {
class="y-axis" class="y-axis"
transform="translate(70, 20)" transform="translate(70, 20)"
/> />
<graph-legend <graph-axis
:graph-width="graphWidth" :graph-width="graphWidth"
:graph-height="graphHeight" :graph-height="graphHeight"
:margin="margin" :margin="margin"
:measurements="measurements" :measurements="measurements"
:legend-title="legendTitle"
:y-axis-label="yAxisLabel" :y-axis-label="yAxisLabel"
:time-series="timeSeries"
:unit-of-display="unitOfDisplay"
:current-data-index="currentDataIndex"
:show-legend-group="showLegend"
/> />
<svg <svg
class="graph-data" class="graph-data"
...@@ -306,5 +299,12 @@ export default { ...@@ -306,5 +299,12 @@ export default {
:deployment-flag-data="deploymentFlagData" :deployment-flag-data="deploymentFlagData"
/> />
</div> </div>
<graph-legend
v-if="showLegend"
:legend-title="legendTitle"
:time-series="timeSeries"
:current-data-index="currentDataIndex"
:unit-of-display="unitOfDisplay"
/>
</div> </div>
</template> </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,
},
yAxisLabel: {
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 +
this.measurements.axisLabelLineOffset) /
2 +
this.yLabelWidth / 2 || 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
class="label-y-axis-line"
stroke="#000000"
stroke-width="1"
x1="10"
y1="0"
:x2="10"
:y2="yPosition"
/>
<rect
class="rect-axis-text"
:transform="rectTransform"
:width="yLabelWidth"
:height="yLabelHeight"
/>
<text
class="label-axis-text y-label-text"
text-anchor="middle"
:transform="textTransform"
ref="ylabel"
>
{{ yAxisLabel }}
</text>
<rect
class="rect-axis-text"
:x="xPosition + 60"
:y="graphHeight - 80"
width="35"
height="50"
/>
<text
class="label-axis-text x-label-text"
:x="xPosition + 60"
:y="yPosition"
dy=".35em"
>
Time
</text>
</g>
</template>
...@@ -160,7 +160,7 @@ export default { ...@@ -160,7 +160,7 @@ export default {
</div> </div>
</div> </div>
<div class="popover-content"> <div class="popover-content">
<table> <table class="prometheus-table">
<tr <tr
v-for="(series, index) in timeSeries" v-for="(series, index) in timeSeries"
:key="index" :key="index"
......
<script> <script>
import { formatRelevantDigits } from '../../../lib/utils/number_utils'; import { formatRelevantDigits } from '~/lib/utils/number_utils';
export default { export default {
props: { props: {
graphWidth: {
type: Number,
required: true,
},
graphHeight: {
type: Number,
required: true,
},
margin: {
type: Object,
required: true,
},
measurements: {
type: Object,
required: true,
},
legendTitle: { legendTitle: {
type: String, type: String,
required: true, required: true,
}, },
yAxisLabel: {
type: String,
required: true,
},
timeSeries: { timeSeries: {
type: Array, type: Array,
required: true, required: true,
}, },
unitOfDisplay: {
type: String,
required: true,
},
currentDataIndex: { currentDataIndex: {
type: Number, type: Number,
required: true, required: true,
}, },
showLegendGroup: { unitOfDisplay: {
type: Boolean, type: String,
required: false, required: true,
default: true,
},
},
data() {
return {
yLabelWidth: 0,
yLabelHeight: 0,
seriesXPosition: 0,
metricUsageXPosition: 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 + this.measurements.axisLabelLineOffset) / 2 +
this.yLabelWidth / 2 || 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.metricUsageXPosition = 0;
this.seriesXPosition = 0;
if (this.$refs.legendTitleSvg != null) {
this.seriesXPosition = this.$refs.legendTitleSvg[0].getBBox().width;
}
if (this.$refs.seriesTitleSvg != null) {
this.metricUsageXPosition = this.$refs.seriesTitleSvg[0].getBBox().width;
}
this.yLabelWidth = bbox.width + 10; // Added some padding
this.yLabelHeight = bbox.height + 5;
});
}, },
methods: { methods: {
translateLegendGroup(index) {
return `translate(0, ${12 * index})`;
},
formatMetricUsage(series) { formatMetricUsage(series) {
const value = const value =
series.values[this.currentDataIndex] && series.values[this.currentDataIndex].value; series.values[this.currentDataIndex] &&
series.values[this.currentDataIndex].value;
if (isNaN(value)) { if (isNaN(value)) {
return '-'; return '-';
} }
return `${formatRelevantDigits(value)} ${this.unitOfDisplay}`; return `${formatRelevantDigits(value)} ${this.unitOfDisplay}`;
}, },
createSeriesString(index, series) { createSeriesString(index, series) {
if (series.metricTag) { if (series.metricTag) {
return `${series.metricTag} ${this.formatMetricUsage(series)}`; return `${series.metricTag} ${this.formatMetricUsage(series)}`;
} }
return `${this.legendTitle} series ${index + 1} ${this.formatMetricUsage(series)}`; return `${this.legendTitle} series ${index + 1} ${this.formatMetricUsage(
series,
)}`;
}, },
summaryMetrics(series) {
return `Avg: ${formatRelevantDigits(series.average)} ${this.unitOfDisplay},
Max: ${formatRelevantDigits(series.max)} ${this.unitOfDisplay}`;
},
strokeDashArray(type) { strokeDashArray(type) {
if (type === 'dashed') return '6, 3'; if (type === 'dashed') return '6, 3';
if (type === 'dotted') return '3, 3'; if (type === 'dotted') return '3, 3';
...@@ -116,89 +54,38 @@ export default { ...@@ -116,89 +54,38 @@ export default {
}; };
</script> </script>
<template> <template>
<g class="axis-label-container"> <div class="prometheus-graph-legends prepend-left-10">
<line <table class="prometheus-table">
class="label-x-axis-line" <tr
stroke="#000000"
stroke-width="1"
x1="10"
:y1="yPosition"
:x2="graphWidth + 20"
:y2="yPosition"
/>
<line
class="label-y-axis-line"
stroke="#000000"
stroke-width="1"
x1="10"
y1="0"
:x2="10"
:y2="yPosition"
/>
<rect
class="rect-axis-text"
:transform="rectTransform"
:width="yLabelWidth"
:height="yLabelHeight"
/>
<text
class="label-axis-text y-label-text"
text-anchor="middle"
:transform="textTransform"
ref="ylabel"
>
{{ yAxisLabel }}
</text>
<rect
class="rect-axis-text"
:x="xPosition + 60"
:y="graphHeight - 80"
width="35"
height="50"
/>
<text
class="label-axis-text x-label-text"
:x="xPosition + 60"
:y="yPosition"
dy=".35em"
>
Time
</text>
<template v-if="showLegendGroup">
<g
class="legend-group"
v-for="(series, index) in timeSeries" v-for="(series, index) in timeSeries"
:key="index" :key="index"
:transform="translateLegendGroup(index)" >
<td>
<svg
width="15"
height="6"
> >
<line <line
:stroke="series.lineColor"
:stroke-width="measurements.legends.height"
:stroke-dasharray="strokeDashArray(series.lineStyle)" :stroke-dasharray="strokeDashArray(series.lineStyle)"
:x1="measurements.legends.offsetX" :stroke="series.lineColor"
:x2="measurements.legends.offsetX + measurements.legends.width" stroke-width="4"
:y1="graphHeight - measurements.legends.offsetY" :x1="0"
:y2="graphHeight - measurements.legends.offsetY" :x2="15"
:y1="2"
:y2="2"
/> />
<text </svg>
v-if="timeSeries.length > 1" </td>
<td
class="legend-metric-title" class="legend-metric-title"
ref="legendTitleSvg" v-if="timeSeries.length > 1"
x="38"
:y="graphHeight - 30"
>
{{ createSeriesString(index, series) }}
</text>
<text
v-else
class="legend-metric-title"
ref="legendTitleSvg"
x="38"
:y="graphHeight - 30"
> >
{{ legendTitle }} {{ formatMetricUsage(series) }} {{ createSeriesString(index, series) }}, {{ summaryMetrics(series) }}
</text> </td>
</g> <td v-else>
</template> {{ legendTitle }} {{ formatMetricUsage(series) }}, {{ summaryMetrics(series) }}
</g> </td>
</tr>
</table>
</div>
</template> </template>
import _ from 'underscore'; import _ from 'underscore';
import { scaleLinear, scaleTime } from 'd3-scale'; import { scaleLinear, scaleTime } from 'd3-scale';
import { line, area, curveLinear } from 'd3-shape'; import { line, area, curveLinear } from 'd3-shape';
import { extent, max } from 'd3-array'; import { extent, max, sum } from 'd3-array';
import { timeMinute } from 'd3-time'; import { timeMinute } from 'd3-time';
const d3 = { scaleLinear, scaleTime, line, area, curveLinear, extent, max, timeMinute }; const d3 = {
scaleLinear,
scaleTime,
line,
area,
curveLinear,
extent,
max,
timeMinute,
sum,
};
const defaultColorPalette = { const defaultColorPalette = {
blue: ['#1f78d1', '#8fbce8'], blue: ['#1f78d1', '#8fbce8'],
...@@ -18,7 +28,15 @@ const defaultColorOrder = ['blue', 'orange', 'red', 'green', 'purple']; ...@@ -18,7 +28,15 @@ const defaultColorOrder = ['blue', 'orange', 'red', 'green', 'purple'];
const defaultStyleOrder = ['solid', 'dashed', 'dotted']; const defaultStyleOrder = ['solid', 'dashed', 'dotted'];
function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom, yDom, lineStyle) { function queryTimeSeries(
query,
graphWidth,
graphHeight,
graphHeightOffset,
xDom,
yDom,
lineStyle,
) {
let usedColors = []; let usedColors = [];
function pickColor(name) { function pickColor(name) {
...@@ -42,11 +60,14 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom ...@@ -42,11 +60,14 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom
let metricTag = ''; let metricTag = '';
let lineColor = ''; let lineColor = '';
let areaColor = ''; let areaColor = '';
const timeSeriesValues = timeSeries.values.map(d => d.value);
const maximumValue = d3.max(timeSeriesValues);
const accum = d3.sum(timeSeriesValues);
const timeSeriesScaleX = d3.scaleTime() const timeSeriesScaleX = d3.scaleTime().range([0, graphWidth - 70]);
.range([0, graphWidth - 70]);
const timeSeriesScaleY = d3.scaleLinear() const timeSeriesScaleY = d3
.scaleLinear()
.range([graphHeight - graphHeightOffset, 0]); .range([graphHeight - graphHeightOffset, 0]);
timeSeriesScaleX.domain(xDom); timeSeriesScaleX.domain(xDom);
...@@ -55,28 +76,35 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom ...@@ -55,28 +76,35 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom
const defined = d => !isNaN(d.value) && d.value != null; const defined = d => !isNaN(d.value) && d.value != null;
const lineFunction = d3.line() const lineFunction = d3
.line()
.defined(defined) .defined(defined)
.curve(d3.curveLinear) // d3 v4 uses curbe instead of interpolate .curve(d3.curveLinear) // d3 v4 uses curbe instead of interpolate
.x(d => timeSeriesScaleX(d.time)) .x(d => timeSeriesScaleX(d.time))
.y(d => timeSeriesScaleY(d.value)); .y(d => timeSeriesScaleY(d.value));
const areaFunction = d3.area() const areaFunction = d3
.area()
.defined(defined) .defined(defined)
.curve(d3.curveLinear) .curve(d3.curveLinear)
.x(d => timeSeriesScaleX(d.time)) .x(d => timeSeriesScaleX(d.time))
.y0(graphHeight - graphHeightOffset) .y0(graphHeight - graphHeightOffset)
.y1(d => timeSeriesScaleY(d.value)); .y1(d => timeSeriesScaleY(d.value));
const timeSeriesMetricLabel = timeSeries.metric[Object.keys(timeSeries.metric)[0]]; const timeSeriesMetricLabel =
const seriesCustomizationData = query.series != null && timeSeries.metric[Object.keys(timeSeries.metric)[0]];
const seriesCustomizationData =
query.series != null &&
_.findWhere(query.series[0].when, { value: timeSeriesMetricLabel }); _.findWhere(query.series[0].when, { value: timeSeriesMetricLabel });
if (seriesCustomizationData) { if (seriesCustomizationData) {
metricTag = seriesCustomizationData.value || timeSeriesMetricLabel; metricTag = seriesCustomizationData.value || timeSeriesMetricLabel;
[lineColor, areaColor] = pickColor(seriesCustomizationData.color); [lineColor, areaColor] = pickColor(seriesCustomizationData.color);
} else { } else {
metricTag = timeSeriesMetricLabel || query.label || `series ${timeSeriesNumber + 1}`; metricTag =
timeSeriesMetricLabel ||
query.label ||
`series ${timeSeriesNumber + 1}`;
[lineColor, areaColor] = pickColor(); [lineColor, areaColor] = pickColor();
} }
...@@ -89,6 +117,8 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom ...@@ -89,6 +117,8 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom
areaPath: areaFunction(timeSeries.values), areaPath: areaFunction(timeSeries.values),
timeSeriesScaleX, timeSeriesScaleX,
values: timeSeries.values, values: timeSeries.values,
max: maximumValue,
average: accum / timeSeries.values.length,
lineStyle, lineStyle,
lineColor, lineColor,
areaColor, areaColor,
...@@ -97,10 +127,22 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom ...@@ -97,10 +127,22 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom
}); });
} }
export default function createTimeSeries(queries, graphWidth, graphHeight, graphHeightOffset) { export default function createTimeSeries(
const allValues = queries.reduce((allQueryResults, query) => allQueryResults.concat( queries,
query.result.reduce((allResults, result) => allResults.concat(result.values), []), graphWidth,
), []); graphHeight,
graphHeightOffset,
) {
const allValues = queries.reduce(
(allQueryResults, query) =>
allQueryResults.concat(
query.result.reduce(
(allResults, result) => allResults.concat(result.values),
[],
),
),
[],
);
const xDom = d3.extent(allValues, d => d.time); const xDom = d3.extent(allValues, d => d.time);
const yDom = [0, d3.max(allValues.map(d => d.value))]; const yDom = [0, d3.max(allValues.map(d => d.value))];
...@@ -108,7 +150,15 @@ export default function createTimeSeries(queries, graphWidth, graphHeight, graph ...@@ -108,7 +150,15 @@ export default function createTimeSeries(queries, graphWidth, graphHeight, graph
return queries.reduce((series, query, index) => { return queries.reduce((series, query, index) => {
const lineStyle = defaultStyleOrder[index % defaultStyleOrder.length]; const lineStyle = defaultStyleOrder[index % defaultStyleOrder.length];
return series.concat( return series.concat(
queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom, yDom, lineStyle), queryTimeSeries(
query,
graphWidth,
graphHeight,
graphHeightOffset,
xDom,
yDom,
lineStyle,
),
); );
}, []); }, []);
} }
...@@ -273,21 +273,6 @@ ...@@ -273,21 +273,6 @@
line-height: 1.2; line-height: 1.2;
} }
table {
border-collapse: collapse;
padding: 0;
margin: 0;
}
td {
vertical-align: middle;
+ td {
padding-left: 5px;
vertical-align: top;
}
}
.deploy-meta-content { .deploy-meta-content {
border-bottom: 1px solid $white-dark; border-bottom: 1px solid $white-dark;
...@@ -323,6 +308,21 @@ ...@@ -323,6 +308,21 @@
} }
} }
.prometheus-table {
border-collapse: collapse;
padding: 0;
margin: 0;
td {
vertical-align: middle;
+ td {
padding-left: 5px;
vertical-align: top;
}
}
}
.prometheus-svg-container { .prometheus-svg-container {
position: relative; position: relative;
height: 0; height: 0;
...@@ -330,8 +330,7 @@ ...@@ -330,8 +330,7 @@
padding: 0; padding: 0;
padding-bottom: 100%; padding-bottom: 100%;
.text-metric-usage, .text-metric-usage {
.legend-metric-title {
fill: $black; fill: $black;
font-weight: $gl-font-weight-normal; font-weight: $gl-font-weight-normal;
font-size: 12px; font-size: 12px;
...@@ -374,10 +373,6 @@ ...@@ -374,10 +373,6 @@
} }
} }
.text-metric-title {
font-size: 12px;
}
.y-label-text, .y-label-text,
.x-label-text { .x-label-text {
fill: $gray-darkest; fill: $gray-darkest;
......
---
title: Add average and maximum summary statistics to the prometheus dashboard
merge_request: 17921
author:
type: changed
import Vue from 'vue';
import GraphAxis from '~/monitoring/components/graph/axis.vue';
import measurements from '~/monitoring/utils/measurements';
const createComponent = propsData => {
const Component = Vue.extend(GraphAxis);
return new Component({
propsData,
}).$mount();
};
const defaultValuesComponent = {
graphWidth: 500,
graphHeight: 300,
graphHeightOffset: 120,
margin: measurements.large.margin,
measurements: measurements.large,
yAxisLabel: 'Values',
};
function getTextFromNode(component, selector) {
return component.$el.querySelector(selector).firstChild.nodeValue.trim();
}
describe('Axis', () => {
describe('Computed props', () => {
it('textTransform', () => {
const component = createComponent(defaultValuesComponent);
expect(component.textTransform).toContain(
'translate(15, 120) rotate(-90)',
);
});
it('xPosition', () => {
const component = createComponent(defaultValuesComponent);
expect(component.xPosition).toEqual(180);
});
it('yPosition', () => {
const component = createComponent(defaultValuesComponent);
expect(component.yPosition).toEqual(240);
});
it('rectTransform', () => {
const component = createComponent(defaultValuesComponent);
expect(component.rectTransform).toContain(
'translate(0, 120) rotate(-90)',
);
});
});
it('has 2 rect-axis-text rect svg elements', () => {
const component = createComponent(defaultValuesComponent);
expect(component.$el.querySelectorAll('.rect-axis-text').length).toEqual(2);
});
it('contains text to signal the usage, title and time with multiple time series', () => {
const component = createComponent(defaultValuesComponent);
expect(getTextFromNode(component, '.y-label-text')).toEqual(
component.yAxisLabel,
);
});
});
import Vue from 'vue'; import Vue from 'vue';
import GraphLegend from '~/monitoring/components/graph/legend.vue'; import GraphLegend from '~/monitoring/components/graph/legend.vue';
import measurements from '~/monitoring/utils/measurements';
import createTimeSeries from '~/monitoring/utils/multiple_time_series'; import createTimeSeries from '~/monitoring/utils/multiple_time_series';
import { singleRowMetricsMultipleSeries, convertDatesMultipleSeries } from '../mock_data'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
import {
const createComponent = (propsData) => { singleRowMetricsMultipleSeries,
const Component = Vue.extend(GraphLegend); convertDatesMultipleSeries,
} from '../mock_data';
return new Component({
propsData,
}).$mount();
};
const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
const defaultValuesComponent = {
graphWidth: 500,
graphHeight: 300,
graphHeightOffset: 120,
margin: measurements.large.margin,
measurements: measurements.large,
areaColorRgb: '#f0f0f0',
legendTitle: 'Title',
yAxisLabel: 'Values',
metricUsage: 'Value',
unitOfDisplay: 'Req/Sec',
currentDataIndex: 0,
};
const timeSeries = createTimeSeries(convertedMetrics[0].queries,
defaultValuesComponent.graphWidth, defaultValuesComponent.graphHeight,
defaultValuesComponent.graphHeightOffset);
defaultValuesComponent.timeSeries = timeSeries; const convertedMetrics = convertDatesMultipleSeries(
singleRowMetricsMultipleSeries,
);
function getTextFromNode(component, selector) { const defaultValuesComponent = {};
return component.$el.querySelector(selector).firstChild.nodeValue.trim();
}
describe('GraphLegend', () => { const timeSeries = createTimeSeries(convertedMetrics[0].queries, 500, 300, 120);
describe('Computed props', () => {
it('textTransform', () => {
const component = createComponent(defaultValuesComponent);
expect(component.textTransform).toContain('translate(15, 120) rotate(-90)'); defaultValuesComponent.timeSeries = timeSeries;
});
it('xPosition', () => { describe('Legend Component', () => {
const component = createComponent(defaultValuesComponent); let vm;
let Legend;
expect(component.xPosition).toEqual(180); beforeEach(() => {
Legend = Vue.extend(GraphLegend);
}); });
it('yPosition', () => { describe('Methods', () => {
const component = createComponent(defaultValuesComponent); beforeEach(() => {
vm = mountComponent(Legend, {
expect(component.yPosition).toEqual(240); legendTitle: 'legend',
timeSeries,
currentDataIndex: 0,
unitOfDisplay: 'Req/Sec',
});
}); });
it('rectTransform', () => { it('formatMetricUsage should contain the unit of display and the current value selected via "currentDataIndex"', () => {
const component = createComponent(defaultValuesComponent); const formattedMetricUsage = vm.formatMetricUsage(timeSeries[0]);
const valueFromSeries = timeSeries[0].values[vm.currentDataIndex].value;
expect(component.rectTransform).toContain('translate(0, 120) rotate(-90)'); expect(formattedMetricUsage.indexOf(vm.unitOfDisplay)).not.toEqual(-1);
}); expect(formattedMetricUsage.indexOf(valueFromSeries)).not.toEqual(-1);
}); });
describe('methods', () => { it('strokeDashArray', () => {
it('translateLegendGroup should only change Y direction', () => { const dashedArray = vm.strokeDashArray('dashed');
const component = createComponent(defaultValuesComponent); const dottedArray = vm.strokeDashArray('dotted');
const translatedCoordinate = component.translateLegendGroup(1); expect(dashedArray).toEqual('6, 3');
expect(translatedCoordinate.indexOf('translate(0, ')).not.toEqual(-1); expect(dottedArray).toEqual('3, 3');
}); });
it('formatMetricUsage should contain the unit of display and the current value selected via "currentDataIndex"', () => { it('summaryMetrics gets the average and max of a series', () => {
const component = createComponent(defaultValuesComponent); const summary = vm.summaryMetrics(timeSeries[0]);
const formattedMetricUsage = component.formatMetricUsage(timeSeries[0]); expect(summary.indexOf('Max')).not.toEqual(-1);
const valueFromSeries = timeSeries[0].values[component.currentDataIndex].value; expect(summary.indexOf('Avg')).not.toEqual(-1);
expect(formattedMetricUsage.indexOf(component.unitOfDisplay)).not.toEqual(-1);
expect(formattedMetricUsage.indexOf(valueFromSeries)).not.toEqual(-1);
}); });
}); });
it('has 2 rect-axis-text rect svg elements', () => { describe('View', () => {
const component = createComponent(defaultValuesComponent); beforeEach(() => {
vm = mountComponent(Legend, {
expect(component.$el.querySelectorAll('.rect-axis-text').length).toEqual(2); legendTitle: 'legend',
timeSeries,
currentDataIndex: 0,
unitOfDisplay: 'Req/Sec',
});
}); });
it('contains text to signal the usage, title and time with multiple time series', () => { it('should render the usage, title and time with multiple time series', () => {
const component = createComponent(defaultValuesComponent); const titles = vm.$el.querySelectorAll('.legend-metric-title');
const titles = component.$el.querySelectorAll('.legend-metric-title');
expect(titles[0].textContent.indexOf('1xx')).not.toEqual(-1); expect(titles[0].textContent.indexOf('1xx')).not.toEqual(-1);
expect(titles[1].textContent.indexOf('2xx')).not.toEqual(-1); expect(titles[1].textContent.indexOf('2xx')).not.toEqual(-1);
expect(getTextFromNode(component, '.y-label-text')).toEqual(component.yAxisLabel);
}); });
it('should contain the same number of legend groups as the timeSeries length', () => { it('should container the same number of rows in the table as time series', () => {
const component = createComponent(defaultValuesComponent); expect(vm.$el.querySelectorAll('.prometheus-table tr').length).toEqual(
vm.timeSeries.length,
expect(component.$el.querySelectorAll('.legend-group').length).toEqual(component.timeSeries.length); );
});
}); });
}); });
...@@ -2,11 +2,15 @@ import Vue from 'vue'; ...@@ -2,11 +2,15 @@ import Vue from 'vue';
import Graph from '~/monitoring/components/graph.vue'; import Graph from '~/monitoring/components/graph.vue';
import MonitoringMixins from '~/monitoring/mixins/monitoring_mixins'; import MonitoringMixins from '~/monitoring/mixins/monitoring_mixins';
import eventHub from '~/monitoring/event_hub'; import eventHub from '~/monitoring/event_hub';
import { deploymentData, convertDatesMultipleSeries, singleRowMetricsMultipleSeries } from './mock_data'; import {
deploymentData,
convertDatesMultipleSeries,
singleRowMetricsMultipleSeries,
} from './mock_data';
const tagsPath = 'http://test.host/frontend-fixtures/environments-project/tags'; const tagsPath = 'http://test.host/frontend-fixtures/environments-project/tags';
const projectPath = 'http://test.host/frontend-fixtures/environments-project'; const projectPath = 'http://test.host/frontend-fixtures/environments-project';
const createComponent = (propsData) => { const createComponent = propsData => {
const Component = Vue.extend(Graph); const Component = Vue.extend(Graph);
return new Component({ return new Component({
...@@ -14,7 +18,9 @@ const createComponent = (propsData) => { ...@@ -14,7 +18,9 @@ const createComponent = (propsData) => {
}).$mount(); }).$mount();
}; };
const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries); const convertedMetrics = convertDatesMultipleSeries(
singleRowMetricsMultipleSeries,
);
describe('Graph', () => { describe('Graph', () => {
beforeEach(() => { beforeEach(() => {
...@@ -31,7 +37,9 @@ describe('Graph', () => { ...@@ -31,7 +37,9 @@ describe('Graph', () => {
projectPath, projectPath,
}); });
expect(component.$el.querySelector('.text-center').innerText.trim()).toBe(component.graphData.title); expect(component.$el.querySelector('.text-center').innerText.trim()).toBe(
component.graphData.title,
);
}); });
describe('Computed props', () => { describe('Computed props', () => {
...@@ -46,8 +54,9 @@ describe('Graph', () => { ...@@ -46,8 +54,9 @@ describe('Graph', () => {
}); });
const transformedHeight = `${component.graphHeight - 100}`; const transformedHeight = `${component.graphHeight - 100}`;
expect(component.axisTransform.indexOf(transformedHeight)) expect(component.axisTransform.indexOf(transformedHeight)).not.toEqual(
.not.toEqual(-1); -1,
);
}); });
it('outerViewBox gets a width and height property based on the DOM size of the element', () => { it('outerViewBox gets a width and height property based on the DOM size of the element', () => {
...@@ -63,11 +72,11 @@ describe('Graph', () => { ...@@ -63,11 +72,11 @@ describe('Graph', () => {
const viewBoxArray = component.outerViewBox.split(' '); const viewBoxArray = component.outerViewBox.split(' ');
expect(typeof component.outerViewBox).toEqual('string'); expect(typeof component.outerViewBox).toEqual('string');
expect(viewBoxArray[2]).toEqual(component.graphWidth.toString()); expect(viewBoxArray[2]).toEqual(component.graphWidth.toString());
expect(viewBoxArray[3]).toEqual(component.graphHeight.toString()); expect(viewBoxArray[3]).toEqual((component.graphHeight - 50).toString());
}); });
}); });
it('sends an event to the eventhub when it has finished resizing', (done) => { it('sends an event to the eventhub when it has finished resizing', done => {
const component = createComponent({ const component = createComponent({
graphData: convertedMetrics[1], graphData: convertedMetrics[1],
classType: 'col-md-6', classType: 'col-md-6',
......
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