Commit d3efad45 authored by Tim Zallmann's avatar Tim Zallmann

Merge branch 'support-additional-colors' into 'master'

Supports additional colors and labels for the prometheus monitoring dashboard

Closes #34629

See merge request !14065
parents bce1c509 4bd5c062
...@@ -13,7 +13,7 @@ export function formatRelevantDigits(number) { ...@@ -13,7 +13,7 @@ export function formatRelevantDigits(number) {
let relevantDigits = 0; let relevantDigits = 0;
let formattedNumber = ''; let formattedNumber = '';
if (!isNaN(Number(number))) { if (!isNaN(Number(number))) {
digitsLeft = number.split('.')[0]; digitsLeft = number.toString().split('.')[0];
switch (digitsLeft.length) { switch (digitsLeft.length) {
case 1: case 1:
relevantDigits = 3; relevantDigits = 3;
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
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';
import monitoringPaths from './monitoring_paths.vue'; import GraphPath from './graph_path.vue';
import MonitoringMixin from '../mixins/monitoring_mixins'; import MonitoringMixin from '../mixins/monitoring_mixins';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import measurements from '../utils/measurements'; import measurements from '../utils/measurements';
...@@ -40,8 +40,6 @@ ...@@ -40,8 +40,6 @@
graphHeightOffset: 120, graphHeightOffset: 120,
margin: {}, margin: {},
unitOfDisplay: '', unitOfDisplay: '',
areaColorRgb: '#8fbce8',
lineColorRgb: '#1f78d1',
yAxisLabel: '', yAxisLabel: '',
legendTitle: '', legendTitle: '',
reducedDeploymentData: [], reducedDeploymentData: [],
...@@ -63,7 +61,7 @@ ...@@ -63,7 +61,7 @@
GraphLegend, GraphLegend,
GraphFlag, GraphFlag,
GraphDeployment, GraphDeployment,
monitoringPaths, GraphPath,
}, },
computed: { computed: {
...@@ -143,7 +141,7 @@ ...@@ -143,7 +141,7 @@
}, },
renderAxesPaths() { renderAxesPaths() {
this.timeSeries = createTimeSeries(this.graphData.queries[0].result, this.timeSeries = createTimeSeries(this.graphData.queries[0],
this.graphWidth, this.graphWidth,
this.graphHeight, this.graphHeight,
this.graphHeightOffset); this.graphHeightOffset);
...@@ -162,7 +160,7 @@ ...@@ -162,7 +160,7 @@
const xAxis = d3.svg.axis() const xAxis = d3.svg.axis()
.scale(axisXScale) .scale(axisXScale)
.ticks(measurements.xTicks) .ticks(d3.time.minute, 60)
.tickFormat(timeScaleFormat) .tickFormat(timeScaleFormat)
.orient('bottom'); .orient('bottom');
...@@ -238,7 +236,7 @@ ...@@ -238,7 +236,7 @@
class="graph-data" class="graph-data"
:viewBox="innerViewBox" :viewBox="innerViewBox"
ref="graphData"> ref="graphData">
<monitoring-paths <graph-path
v-for="(path, index) in timeSeries" v-for="(path, index) in timeSeries"
:key="index" :key="index"
:generated-line-path="path.linePath" :generated-line-path="path.linePath"
...@@ -246,7 +244,7 @@ ...@@ -246,7 +244,7 @@
:line-color="path.lineColor" :line-color="path.lineColor"
:area-color="path.areaColor" :area-color="path.areaColor"
/> />
<monitoring-deployment <graph-deployment
:show-deploy-info="showDeployInfo" :show-deploy-info="showDeployInfo"
:deployment-data="reducedDeploymentData" :deployment-data="reducedDeploymentData"
:graph-height="graphHeight" :graph-height="graphHeight"
......
...@@ -81,6 +81,13 @@ ...@@ -81,6 +81,13 @@
formatMetricUsage(series) { formatMetricUsage(series) {
return `${formatRelevantDigits(series.values[this.currentDataIndex].value)} ${this.unitOfDisplay}`; return `${formatRelevantDigits(series.values[this.currentDataIndex].value)} ${this.unitOfDisplay}`;
}, },
createSeriesString(index, series) {
if (series.metricTag) {
return `${series.metricTag} ${this.formatMetricUsage(series)}`;
}
return `${this.legendTitle} series ${index + 1} ${this.formatMetricUsage(series)}`;
},
}, },
mounted() { mounted() {
this.$nextTick(() => { this.$nextTick(() => {
...@@ -164,7 +171,7 @@ ...@@ -164,7 +171,7 @@
ref="legendTitleSvg" ref="legendTitleSvg"
x="38" x="38"
:y="graphHeight - 30"> :y="graphHeight - 30">
{{legendTitle}} Series {{index + 1}} {{formatMetricUsage(series)}} {{createSeriesString(index, series)}}
</text> </text>
<text <text
v-else v-else
......
import d3 from 'd3'; import d3 from 'd3';
import _ from 'underscore'; import _ from 'underscore';
export default function createTimeSeries(seriesData, graphWidth, graphHeight, graphHeightOffset) { const defaultColorPalette = {
const maxValues = seriesData.map((timeSeries, index) => { blue: ['#1f78d1', '#8fbce8'],
orange: ['#fc9403', '#feca81'],
red: ['#db3b21', '#ed9d90'],
green: ['#1aaa55', '#8dd5aa'],
purple: ['#6666c4', '#d1d1f0'],
};
const defaultColorOrder = ['blue', 'orange', 'red', 'green', 'purple'];
export default function createTimeSeries(queryData, graphWidth, graphHeight, graphHeightOffset) {
let usedColors = [];
function pickColor(name) {
let pick;
if (name && defaultColorPalette[name]) {
pick = name;
} else {
const unusedColors = _.difference(defaultColorOrder, usedColors);
if (unusedColors.length > 0) {
pick = unusedColors[0];
} else {
usedColors = [];
pick = defaultColorOrder[0];
}
}
usedColors.push(pick);
return defaultColorPalette[pick];
}
const maxValues = queryData.result.map((timeSeries, index) => {
const maxValue = d3.max(timeSeries.values.map(d => d.value)); const maxValue = d3.max(timeSeries.values.map(d => d.value));
return { return {
maxValue, maxValue,
...@@ -12,10 +41,11 @@ export default function createTimeSeries(seriesData, graphWidth, graphHeight, gr ...@@ -12,10 +41,11 @@ export default function createTimeSeries(seriesData, graphWidth, graphHeight, gr
const maxValueFromSeries = _.max(maxValues, val => val.maxValue); const maxValueFromSeries = _.max(maxValues, val => val.maxValue);
let timeSeriesNumber = 1; return queryData.result.map((timeSeries, timeSeriesNumber) => {
let lineColor = '#1f78d1'; let metricTag = '';
let areaColor = '#8fbce8'; let lineColor = '';
return seriesData.map((timeSeries) => { let areaColor = '';
const timeSeriesScaleX = d3.time.scale() const timeSeriesScaleX = d3.time.scale()
.range([0, graphWidth - 70]); .range([0, graphWidth - 70]);
...@@ -23,49 +53,30 @@ export default function createTimeSeries(seriesData, graphWidth, graphHeight, gr ...@@ -23,49 +53,30 @@ export default function createTimeSeries(seriesData, graphWidth, graphHeight, gr
.range([graphHeight - graphHeightOffset, 0]); .range([graphHeight - graphHeightOffset, 0]);
timeSeriesScaleX.domain(d3.extent(timeSeries.values, d => d.time)); timeSeriesScaleX.domain(d3.extent(timeSeries.values, d => d.time));
timeSeriesScaleX.ticks(d3.time.minute, 60);
timeSeriesScaleY.domain([0, maxValueFromSeries.maxValue]); timeSeriesScaleY.domain([0, maxValueFromSeries.maxValue]);
const lineFunction = d3.svg.line() const lineFunction = d3.svg.line()
.interpolate('linear')
.x(d => timeSeriesScaleX(d.time)) .x(d => timeSeriesScaleX(d.time))
.y(d => timeSeriesScaleY(d.value)); .y(d => timeSeriesScaleY(d.value));
const areaFunction = d3.svg.area() const areaFunction = d3.svg.area()
.interpolate('linear')
.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));
.interpolate('linear');
switch (timeSeriesNumber) {
case 1:
lineColor = '#1f78d1';
areaColor = '#8fbce8';
break;
case 2:
lineColor = '#fc9403';
areaColor = '#feca81';
break;
case 3:
lineColor = '#db3b21';
areaColor = '#ed9d90';
break;
case 4:
lineColor = '#1aaa55';
areaColor = '#8dd5aa';
break;
case 5:
lineColor = '#6666c4';
areaColor = '#d1d1f0';
break;
default:
lineColor = '#1f78d1';
areaColor = '#8fbce8';
break;
}
if (timeSeriesNumber <= 5) { const timeSeriesMetricLabel = timeSeries.metric[Object.keys(timeSeries.metric)[0]];
timeSeriesNumber = timeSeriesNumber += 1; const seriesCustomizationData = queryData.series != null &&
_.findWhere(queryData.series[0].when,
{ value: timeSeriesMetricLabel });
if (seriesCustomizationData != null) {
metricTag = seriesCustomizationData.value || timeSeriesMetricLabel;
[lineColor, areaColor] = pickColor(seriesCustomizationData.color);
} else { } else {
timeSeriesNumber = 1; metricTag = timeSeriesMetricLabel || `series ${timeSeriesNumber + 1}`;
[lineColor, areaColor] = pickColor();
} }
return { return {
...@@ -75,6 +86,7 @@ export default function createTimeSeries(seriesData, graphWidth, graphHeight, gr ...@@ -75,6 +86,7 @@ export default function createTimeSeries(seriesData, graphWidth, graphHeight, gr
values: timeSeries.values, values: timeSeries.values,
lineColor, lineColor,
areaColor, areaColor,
metricTag,
}; };
}); });
} }
---
title: Added support for specific labels and colors
merge_request:
author:
type: changed
...@@ -28,7 +28,7 @@ const defaultValuesComponent = { ...@@ -28,7 +28,7 @@ const defaultValuesComponent = {
currentDataIndex: 0, currentDataIndex: 0,
}; };
const timeSeries = createTimeSeries(convertedMetrics[0].queries[0].result, const timeSeries = createTimeSeries(convertedMetrics[0].queries[0],
defaultValuesComponent.graphWidth, defaultValuesComponent.graphHeight, defaultValuesComponent.graphWidth, defaultValuesComponent.graphHeight,
defaultValuesComponent.graphHeightOffset); defaultValuesComponent.graphHeightOffset);
...@@ -89,13 +89,12 @@ describe('GraphLegend', () => { ...@@ -89,13 +89,12 @@ describe('GraphLegend', () => {
expect(component.$el.querySelectorAll('.rect-axis-text').length).toEqual(2); expect(component.$el.querySelectorAll('.rect-axis-text').length).toEqual(2);
}); });
it('contains text to signal the usage, title and time', () => { it('contains text to signal the usage, title and time with multiple time series', () => {
const component = createComponent(defaultValuesComponent); const component = createComponent(defaultValuesComponent);
const titles = component.$el.querySelectorAll('.legend-metric-title'); const titles = component.$el.querySelectorAll('.legend-metric-title');
expect(getTextFromNode(component, '.legend-metric-title').indexOf(component.legendTitle)).not.toEqual(-1); expect(titles[0].textContent.indexOf('1xx')).not.toEqual(-1);
expect(titles[0].textContent.indexOf('Title')).not.toEqual(-1); expect(titles[1].textContent.indexOf('2xx')).not.toEqual(-1);
expect(titles[1].textContent.indexOf('Series')).not.toEqual(-1);
expect(getTextFromNode(component, '.y-label-text')).toEqual(component.yAxisLabel); expect(getTextFromNode(component, '.y-label-text')).toEqual(component.yAxisLabel);
}); });
......
import Vue from 'vue'; import Vue from 'vue';
import MonitoringPaths from '~/monitoring/components/monitoring_paths.vue'; import GraphPath from '~/monitoring/components/graph_path.vue';
import createTimeSeries from '~/monitoring/utils/multiple_time_series'; import createTimeSeries from '~/monitoring/utils/multiple_time_series';
import { singleRowMetricsMultipleSeries, convertDatesMultipleSeries } from './mock_data'; import { singleRowMetricsMultipleSeries, convertDatesMultipleSeries } from './mock_data';
const createComponent = (propsData) => { const createComponent = (propsData) => {
const Component = Vue.extend(MonitoringPaths); const Component = Vue.extend(GraphPath);
return new Component({ return new Component({
propsData, propsData,
...@@ -13,22 +13,23 @@ const createComponent = (propsData) => { ...@@ -13,22 +13,23 @@ const createComponent = (propsData) => {
const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries); const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
const timeSeries = createTimeSeries(convertedMetrics[0].queries[0].result, 428, 272, 120); const timeSeries = createTimeSeries(convertedMetrics[0].queries[0], 428, 272, 120);
const firstTimeSeries = timeSeries[0];
describe('Monitoring Paths', () => { describe('Monitoring Paths', () => {
it('renders two paths to represent a line and the area underneath it', () => { it('renders two paths to represent a line and the area underneath it', () => {
const component = createComponent({ const component = createComponent({
generatedLinePath: timeSeries[0].linePath, generatedLinePath: firstTimeSeries.linePath,
generatedAreaPath: timeSeries[0].areaPath, generatedAreaPath: firstTimeSeries.areaPath,
lineColor: '#ccc', lineColor: firstTimeSeries.lineColor,
areaColor: '#fff', areaColor: firstTimeSeries.areaColor,
}); });
const metricArea = component.$el.querySelector('.metric-area'); const metricArea = component.$el.querySelector('.metric-area');
const metricLine = component.$el.querySelector('.metric-line'); const metricLine = component.$el.querySelector('.metric-line');
expect(metricArea.getAttribute('fill')).toBe('#fff'); expect(metricArea.getAttribute('fill')).toBe('#8fbce8');
expect(metricArea.getAttribute('d')).toBe(timeSeries[0].areaPath); expect(metricArea.getAttribute('d')).toBe(firstTimeSeries.areaPath);
expect(metricLine.getAttribute('stroke')).toBe('#ccc'); expect(metricLine.getAttribute('stroke')).toBe('#1f78d1');
expect(metricLine.getAttribute('d')).toBe(timeSeries[0].linePath); expect(metricLine.getAttribute('d')).toBe(firstTimeSeries.linePath);
}); });
}); });
...@@ -6346,7 +6346,13 @@ export const singleRowMetricsMultipleSeries = [ ...@@ -6346,7 +6346,13 @@ export const singleRowMetricsMultipleSeries = [
} }
] ]
}, },
] ],
'when': [
{
'value': 'hundred(s)',
'color': 'green',
},
],
} }
] ]
}, },
......
...@@ -2,16 +2,17 @@ import createTimeSeries from '~/monitoring/utils/multiple_time_series'; ...@@ -2,16 +2,17 @@ import createTimeSeries from '~/monitoring/utils/multiple_time_series';
import { convertDatesMultipleSeries, singleRowMetricsMultipleSeries } from '../mock_data'; import { convertDatesMultipleSeries, singleRowMetricsMultipleSeries } from '../mock_data';
const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries); const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
const timeSeries = createTimeSeries(convertedMetrics[0].queries[0].result, 428, 272, 120); const timeSeries = createTimeSeries(convertedMetrics[0].queries[0], 428, 272, 120);
const firstTimeSeries = timeSeries[0];
describe('Multiple time series', () => { describe('Multiple time series', () => {
it('createTimeSeries returned array contains an object for each element', () => { it('createTimeSeries returned array contains an object for each element', () => {
expect(typeof timeSeries[0].linePath).toEqual('string'); expect(typeof firstTimeSeries.linePath).toEqual('string');
expect(typeof timeSeries[0].areaPath).toEqual('string'); expect(typeof firstTimeSeries.areaPath).toEqual('string');
expect(typeof timeSeries[0].timeSeriesScaleX).toEqual('function'); expect(typeof firstTimeSeries.timeSeriesScaleX).toEqual('function');
expect(typeof timeSeries[0].areaColor).toEqual('string'); expect(typeof firstTimeSeries.areaColor).toEqual('string');
expect(typeof timeSeries[0].lineColor).toEqual('string'); expect(typeof firstTimeSeries.lineColor).toEqual('string');
expect(timeSeries[0].values instanceof Array).toEqual(true); expect(firstTimeSeries.values instanceof Array).toEqual(true);
}); });
it('createTimeSeries returns an array', () => { it('createTimeSeries returns an array', () => {
......
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