Commit ac78b6dd authored by Clement Ho's avatar Clement Ho

Merge branch 'ce-9929-cluster-monitoring-is-broken-after-echarts' into 'master'

Backport: Fix cluster health charts

See merge request gitlab-org/gitlab-ce!25823
parents 78a01253 b7c4ddfc
...@@ -4,6 +4,7 @@ import dateFormat from 'dateformat'; ...@@ -4,6 +4,7 @@ import dateFormat from 'dateformat';
import { debounceByAnimationFrame } from '~/lib/utils/common_utils'; import { debounceByAnimationFrame } from '~/lib/utils/common_utils';
import { getSvgIconPathContent } from '~/lib/utils/icon_utils'; import { getSvgIconPathContent } from '~/lib/utils/icon_utils';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import { chartHeight, graphTypes, lineTypes } from '../../constants';
let debouncedResize; let debouncedResize;
...@@ -19,7 +20,6 @@ export default { ...@@ -19,7 +20,6 @@ export default {
required: true, required: true,
validator(data) { validator(data) {
return ( return (
data.queries &&
Array.isArray(data.queries) && Array.isArray(data.queries) &&
data.queries.filter(query => { data.queries.filter(query => {
if (Array.isArray(query.result)) { if (Array.isArray(query.result)) {
...@@ -51,21 +51,44 @@ export default { ...@@ -51,21 +51,44 @@ export default {
return { return {
tooltip: { tooltip: {
title: '', title: '',
content: '', content: [],
isDeployment: false, isDeployment: false,
sha: '', sha: '',
}, },
width: 0, width: 0,
height: 0, height: chartHeight,
svgs: {}, svgs: {},
primaryColor: null,
}; };
}, },
computed: { computed: {
chartData() { chartData() {
return this.graphData.queries.reduce((accumulator, query) => { return this.graphData.queries.map(query => {
accumulator[query.unit] = query.result.reduce((acc, res) => acc.concat(res.values), []); const { appearance } = query;
return accumulator; const lineType =
}, {}); appearance && appearance.line && appearance.line.type
? appearance.line.type
: lineTypes.default;
const lineColor = lineType === lineTypes.threshold ? this.primaryColor : undefined;
return {
name: this.formatLegendLabel(query),
data: this.concatenateResults(query.result),
lineStyle: {
type: lineType,
color: lineColor,
},
itemStyle: {
color: lineColor,
},
areaStyle: {
opacity:
appearance && appearance.area && typeof appearance.area.opacity === 'number'
? appearance.area.opacity
: undefined,
},
};
});
}, },
chartOptions() { chartOptions() {
return { return {
...@@ -85,9 +108,6 @@ export default { ...@@ -85,9 +108,6 @@ export default {
formatter: value => value.toFixed(3), formatter: value => value.toFixed(3),
}, },
}, },
legend: {
formatter: this.xAxisLabel,
},
series: this.scatterSeries, series: this.scatterSeries,
dataZoom: this.dataZoomConfig, dataZoom: this.dataZoomConfig,
}; };
...@@ -98,8 +118,8 @@ export default { ...@@ -98,8 +118,8 @@ export default {
return handleIcon ? { handleIcon } : {}; return handleIcon ? { handleIcon } : {};
}, },
earliestDatapoint() { earliestDatapoint() {
return Object.values(this.chartData).reduce((acc, data) => { return this.chartData.reduce((acc, series) => {
const [[timestamp]] = data.sort(([a], [b]) => { const [[timestamp]] = series.data.sort(([a], [b]) => {
if (a < b) { if (a < b) {
return -1; return -1;
} }
...@@ -129,14 +149,14 @@ export default { ...@@ -129,14 +149,14 @@ export default {
}, },
scatterSeries() { scatterSeries() {
return { return {
type: 'scatter', type: graphTypes.deploymentData,
data: this.recentDeployments.map(deployment => [deployment.createdAt, 0]), data: this.recentDeployments.map(deployment => [deployment.createdAt, 0]),
symbol: this.svgs.rocket, symbol: this.svgs.rocket,
symbolSize: 14, symbolSize: 14,
}; itemStyle: {
color: this.primaryColor,
}, },
xAxisLabel() { };
return this.graphData.queries.map(query => query.label).join(', ');
}, },
yAxisLabel() { yAxisLabel() {
return `${this.graphData.y_label}`; return `${this.graphData.y_label}`;
...@@ -155,18 +175,34 @@ export default { ...@@ -155,18 +175,34 @@ export default {
this.setSvg('scroll-handle'); this.setSvg('scroll-handle');
}, },
methods: { methods: {
concatenateResults(results) {
return results.reduce((acc, result) => acc.concat(result.values), []);
},
formatLegendLabel(query) {
return `${query.label}`;
},
formatTooltipText(params) { formatTooltipText(params) {
const [seriesData] = params.seriesData;
this.tooltip.isDeployment = seriesData.componentSubType === 'scatter';
this.tooltip.title = dateFormat(params.value, 'dd mmm yyyy, h:MMTT'); this.tooltip.title = dateFormat(params.value, 'dd mmm yyyy, h:MMTT');
if (this.tooltip.isDeployment) { this.tooltip.content = [];
params.seriesData.forEach(seriesData => {
if (seriesData.componentSubType === graphTypes.deploymentData) {
this.tooltip.isDeployment = true;
const [deploy] = this.recentDeployments.filter( const [deploy] = this.recentDeployments.filter(
deployment => deployment.createdAt === seriesData.value[0], deployment => deployment.createdAt === seriesData.value[0],
); );
this.tooltip.sha = deploy.sha.substring(0, 8); this.tooltip.sha = deploy.sha.substring(0, 8);
} else { } else {
this.tooltip.content = `${this.yAxisLabel} ${seriesData.value[1].toFixed(3)}`; const { seriesName } = seriesData;
// seriesData.value contains the chart's [x, y] value pair
// seriesData.value[1] is threfore the chart y value
const value = seriesData.value[1].toFixed(3);
this.tooltip.content.push({
name: seriesName,
value,
});
} }
});
}, },
setSvg(name) { setSvg(name) {
getSvgIconPathContent(name) getSvgIconPathContent(name)
...@@ -177,10 +213,12 @@ export default { ...@@ -177,10 +213,12 @@ export default {
}) })
.catch(() => {}); .catch(() => {});
}, },
onChartUpdated(chart) {
[this.primaryColor] = chart.getOption().color;
},
onResize() { onResize() {
const { width, height } = this.$refs.areaChart.$el.getBoundingClientRect(); const { width } = this.$refs.areaChart.$el.getBoundingClientRect();
this.width = width; this.width = width;
this.height = height;
}, },
}, },
}; };
...@@ -201,6 +239,7 @@ export default { ...@@ -201,6 +239,7 @@ export default {
:thresholds="alertData" :thresholds="alertData"
:width="width" :width="width"
:height="height" :height="height"
@updated="onChartUpdated"
> >
<template slot="tooltipTitle"> <template slot="tooltipTitle">
<div v-if="tooltip.isDeployment"> <div v-if="tooltip.isDeployment">
...@@ -214,7 +253,13 @@ export default { ...@@ -214,7 +253,13 @@ export default {
{{ tooltip.sha }} {{ tooltip.sha }}
</div> </div>
<template v-else> <template v-else>
{{ tooltip.content }} <div
v-for="(content, key) in tooltip.content"
:key="key"
class="d-flex justify-content-between"
>
{{ content.name }} {{ content.value }}
</div>
</template> </template>
</template> </template>
</gl-area-chart> </gl-area-chart>
......
export const chartHeight = 300;
export const graphTypes = {
deploymentData: 'scatter',
};
export const lineTypes = {
default: 'solid',
threshold: 'dashed',
};
...@@ -75,15 +75,6 @@ describe('Area component', () => { ...@@ -75,15 +75,6 @@ describe('Area component', () => {
expect(shallowWrapperContainsSlotText(glAreaChart, 'tooltipTitle', mockTitle)).toBe(true); expect(shallowWrapperContainsSlotText(glAreaChart, 'tooltipTitle', mockTitle)).toBe(true);
}); });
it('recieves tooltip content', () => {
const mockContent = 'mockContent';
areaChart.vm.tooltip.content = mockContent;
expect(shallowWrapperContainsSlotText(glAreaChart, 'tooltipContent', mockContent)).toBe(
true,
);
});
describe('when tooltip is showing deployment data', () => { describe('when tooltip is showing deployment data', () => {
beforeEach(() => { beforeEach(() => {
areaChart.vm.tooltip.isDeployment = true; areaChart.vm.tooltip.isDeployment = true;
...@@ -111,6 +102,7 @@ describe('Area component', () => { ...@@ -111,6 +102,7 @@ describe('Area component', () => {
const generateSeriesData = type => ({ const generateSeriesData = type => ({
seriesData: [ seriesData: [
{ {
seriesName: areaChart.vm.chartData[0].name,
componentSubType: type, componentSubType: type,
value: [mockDate, 5.55555], value: [mockDate, 5.55555],
}, },
...@@ -128,7 +120,14 @@ describe('Area component', () => { ...@@ -128,7 +120,14 @@ describe('Area component', () => {
}); });
it('formats tooltip content', () => { it('formats tooltip content', () => {
expect(areaChart.vm.tooltip.content).toBe('CPU 5.556'); expect(areaChart.vm.tooltip.content).toEqual([{ name: 'Core Usage', value: '5.556' }]);
expect(
shallowWrapperContainsSlotText(
areaChart.find(GlAreaChart),
'tooltipContent',
'Core Usage 5.556',
),
).toBe(true);
}); });
}); });
...@@ -168,12 +167,10 @@ describe('Area component', () => { ...@@ -168,12 +167,10 @@ describe('Area component', () => {
describe('onResize', () => { describe('onResize', () => {
const mockWidth = 233; const mockWidth = 233;
const mockHeight = 144;
beforeEach(() => { beforeEach(() => {
spyOn(Element.prototype, 'getBoundingClientRect').and.callFake(() => ({ spyOn(Element.prototype, 'getBoundingClientRect').and.callFake(() => ({
width: mockWidth, width: mockWidth,
height: mockHeight,
})); }));
areaChart.vm.onResize(); areaChart.vm.onResize();
}); });
...@@ -181,22 +178,25 @@ describe('Area component', () => { ...@@ -181,22 +178,25 @@ describe('Area component', () => {
it('sets area chart width', () => { it('sets area chart width', () => {
expect(areaChart.vm.width).toBe(mockWidth); expect(areaChart.vm.width).toBe(mockWidth);
}); });
it('sets area chart height', () => {
expect(areaChart.vm.height).toBe(mockHeight);
});
}); });
}); });
describe('computed', () => { describe('computed', () => {
describe('chartData', () => { describe('chartData', () => {
let chartData;
const seriesData = () => chartData[0];
beforeEach(() => {
({ chartData } = areaChart.vm);
});
it('utilizes all data points', () => { it('utilizes all data points', () => {
expect(Object.keys(areaChart.vm.chartData)).toEqual(['Cores']); expect(chartData.length).toBe(1);
expect(areaChart.vm.chartData.Cores.length).toBe(297); expect(seriesData().data.length).toBe(297);
}); });
it('creates valid data', () => { it('creates valid data', () => {
const data = areaChart.vm.chartData.Cores; const { data } = seriesData();
expect( expect(
data.filter(([time, value]) => new Date(time).getTime() > 0 && typeof value === 'number') data.filter(([time, value]) => new Date(time).getTime() > 0 && typeof value === 'number')
...@@ -215,12 +215,6 @@ describe('Area component', () => { ...@@ -215,12 +215,6 @@ describe('Area component', () => {
}); });
}); });
describe('xAxisLabel', () => {
it('constructs a label for the chart x-axis', () => {
expect(areaChart.vm.xAxisLabel).toBe('Core Usage');
});
});
describe('yAxisLabel', () => { describe('yAxisLabel', () => {
it('constructs a label for the chart y-axis', () => { it('constructs a label for the chart y-axis', () => {
expect(areaChart.vm.yAxisLabel).toBe('CPU'); expect(areaChart.vm.yAxisLabel).toBe('CPU');
......
...@@ -663,10 +663,10 @@ ...@@ -663,10 +663,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.54.0.tgz#00320e845efd46716042cde0c348b990d4908daf" resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.54.0.tgz#00320e845efd46716042cde0c348b990d4908daf"
integrity sha512-DR17iy8TM5IbXEacqiDP0p8SuC/J8EL+98xbfVz5BKvRsPHpeZJQNlBF/petIV5d+KWM5A9v3GZTY7uMU7z/JQ== integrity sha512-DR17iy8TM5IbXEacqiDP0p8SuC/J8EL+98xbfVz5BKvRsPHpeZJQNlBF/petIV5d+KWM5A9v3GZTY7uMU7z/JQ==
"@gitlab/ui@^2.2.0": "@gitlab/ui@^2.2.1":
version "2.2.0" version "2.2.1"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-2.2.0.tgz#8e384d3fb3d84f2886eacea75feb05e0ea42adcc" resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-2.2.1.tgz#4b4f5c9234279a2ebeafc337c58eb6460aef963b"
integrity sha512-CCr1CjFyeycm1vrTtRKng5VknWWTN3fFw48YQThz/rgg0viVtA12oqz7oqGGAC+AktnWXtA/cxkXjVNpKTmEpA== integrity sha512-hxgZi1KRwd8EmVs1OeE/zcVH3GCE523GK4JWrTD8x9xPRS0O+NSIvbgqGBXp25UiG7jei9Up0X9BzZMTmZimug==
dependencies: dependencies:
"@babel/standalone" "^7.0.0" "@babel/standalone" "^7.0.0"
bootstrap-vue "^2.0.0-rc.11" bootstrap-vue "^2.0.0-rc.11"
......
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