diff options
-rw-r--r-- | app/assets/javascripts/monitoring/components/charts/area.vue | 103 | ||||
-rw-r--r-- | app/assets/javascripts/monitoring/constants.js | 10 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | spec/javascripts/monitoring/charts/area_spec.js | 44 | ||||
-rw-r--r-- | yarn.lock | 8 |
5 files changed, 108 insertions, 59 deletions
diff --git a/app/assets/javascripts/monitoring/components/charts/area.vue b/app/assets/javascripts/monitoring/components/charts/area.vue index 17e4f325b08..41783d311ef 100644 --- a/app/assets/javascripts/monitoring/components/charts/area.vue +++ b/app/assets/javascripts/monitoring/components/charts/area.vue @@ -4,6 +4,7 @@ import dateFormat from 'dateformat'; import { debounceByAnimationFrame } from '~/lib/utils/common_utils'; import { getSvgIconPathContent } from '~/lib/utils/icon_utils'; import Icon from '~/vue_shared/components/icon.vue'; +import { chartHeight, graphTypes, lineTypes } from '../../constants'; let debouncedResize; @@ -19,7 +20,6 @@ export default { required: true, validator(data) { return ( - data.queries && Array.isArray(data.queries) && data.queries.filter(query => { if (Array.isArray(query.result)) { @@ -51,21 +51,44 @@ export default { return { tooltip: { title: '', - content: '', + content: [], isDeployment: false, sha: '', }, width: 0, - height: 0, + height: chartHeight, svgs: {}, + primaryColor: null, }; }, computed: { chartData() { - return this.graphData.queries.reduce((accumulator, query) => { - accumulator[query.unit] = query.result.reduce((acc, res) => acc.concat(res.values), []); - return accumulator; - }, {}); + return this.graphData.queries.map(query => { + const { appearance } = query; + 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() { return { @@ -85,9 +108,6 @@ export default { formatter: value => value.toFixed(3), }, }, - legend: { - formatter: this.xAxisLabel, - }, series: this.scatterSeries, dataZoom: this.dataZoomConfig, }; @@ -98,8 +118,8 @@ export default { return handleIcon ? { handleIcon } : {}; }, earliestDatapoint() { - return Object.values(this.chartData).reduce((acc, data) => { - const [[timestamp]] = data.sort(([a], [b]) => { + return this.chartData.reduce((acc, series) => { + const [[timestamp]] = series.data.sort(([a], [b]) => { if (a < b) { return -1; } @@ -129,15 +149,15 @@ export default { }, scatterSeries() { return { - type: 'scatter', + type: graphTypes.deploymentData, data: this.recentDeployments.map(deployment => [deployment.createdAt, 0]), symbol: this.svgs.rocket, symbolSize: 14, + itemStyle: { + color: this.primaryColor, + }, }; }, - xAxisLabel() { - return this.graphData.queries.map(query => query.label).join(', '); - }, yAxisLabel() { return `${this.graphData.y_label}`; }, @@ -155,18 +175,34 @@ export default { this.setSvg('scroll-handle'); }, methods: { + concatenateResults(results) { + return results.reduce((acc, result) => acc.concat(result.values), []); + }, + formatLegendLabel(query) { + return `${query.label}`; + }, formatTooltipText(params) { - const [seriesData] = params.seriesData; - this.tooltip.isDeployment = seriesData.componentSubType === 'scatter'; this.tooltip.title = dateFormat(params.value, 'dd mmm yyyy, h:MMTT'); - if (this.tooltip.isDeployment) { - const [deploy] = this.recentDeployments.filter( - deployment => deployment.createdAt === seriesData.value[0], - ); - this.tooltip.sha = deploy.sha.substring(0, 8); - } else { - this.tooltip.content = `${this.yAxisLabel} ${seriesData.value[1].toFixed(3)}`; - } + this.tooltip.content = []; + params.seriesData.forEach(seriesData => { + if (seriesData.componentSubType === graphTypes.deploymentData) { + this.tooltip.isDeployment = true; + const [deploy] = this.recentDeployments.filter( + deployment => deployment.createdAt === seriesData.value[0], + ); + this.tooltip.sha = deploy.sha.substring(0, 8); + } else { + 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) { getSvgIconPathContent(name) @@ -177,10 +213,12 @@ export default { }) .catch(() => {}); }, + onChartUpdated(chart) { + [this.primaryColor] = chart.getOption().color; + }, onResize() { - const { width, height } = this.$refs.areaChart.$el.getBoundingClientRect(); + const { width } = this.$refs.areaChart.$el.getBoundingClientRect(); this.width = width; - this.height = height; }, }, }; @@ -201,6 +239,7 @@ export default { :thresholds="alertData" :width="width" :height="height" + @updated="onChartUpdated" > <template slot="tooltipTitle"> <div v-if="tooltip.isDeployment"> @@ -214,7 +253,13 @@ export default { {{ tooltip.sha }} </div> <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> </gl-area-chart> diff --git a/app/assets/javascripts/monitoring/constants.js b/app/assets/javascripts/monitoring/constants.js new file mode 100644 index 00000000000..869173b6572 --- /dev/null +++ b/app/assets/javascripts/monitoring/constants.js @@ -0,0 +1,10 @@ +export const chartHeight = 300; + +export const graphTypes = { + deploymentData: 'scatter', +}; + +export const lineTypes = { + default: 'solid', + threshold: 'dashed', +}; diff --git a/package.json b/package.json index 578e6873495..b9dc2220f56 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "@babel/preset-env": "^7.3.1", "@gitlab/csslab": "^1.8.0", "@gitlab/svgs": "^1.54.0", - "@gitlab/ui": "^2.2.0", + "@gitlab/ui": "^2.2.1", "apollo-boost": "^0.3.1", "apollo-client": "^2.5.1", "autosize": "^4.0.0", diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js index 1b6fc456ceb..fb49290be19 100644 --- a/spec/javascripts/monitoring/charts/area_spec.js +++ b/spec/javascripts/monitoring/charts/area_spec.js @@ -75,15 +75,6 @@ describe('Area component', () => { 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', () => { beforeEach(() => { areaChart.vm.tooltip.isDeployment = true; @@ -111,6 +102,7 @@ describe('Area component', () => { const generateSeriesData = type => ({ seriesData: [ { + seriesName: areaChart.vm.chartData[0].name, componentSubType: type, value: [mockDate, 5.55555], }, @@ -128,7 +120,14 @@ describe('Area component', () => { }); 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', () => { describe('onResize', () => { const mockWidth = 233; - const mockHeight = 144; beforeEach(() => { spyOn(Element.prototype, 'getBoundingClientRect').and.callFake(() => ({ width: mockWidth, - height: mockHeight, })); areaChart.vm.onResize(); }); @@ -181,22 +178,25 @@ describe('Area component', () => { it('sets area chart width', () => { expect(areaChart.vm.width).toBe(mockWidth); }); - - it('sets area chart height', () => { - expect(areaChart.vm.height).toBe(mockHeight); - }); }); }); describe('computed', () => { describe('chartData', () => { + let chartData; + const seriesData = () => chartData[0]; + + beforeEach(() => { + ({ chartData } = areaChart.vm); + }); + it('utilizes all data points', () => { - expect(Object.keys(areaChart.vm.chartData)).toEqual(['Cores']); - expect(areaChart.vm.chartData.Cores.length).toBe(297); + expect(chartData.length).toBe(1); + expect(seriesData().data.length).toBe(297); }); it('creates valid data', () => { - const data = areaChart.vm.chartData.Cores; + const { data } = seriesData(); expect( data.filter(([time, value]) => new Date(time).getTime() > 0 && typeof value === 'number') @@ -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', () => { it('constructs a label for the chart y-axis', () => { expect(areaChart.vm.yAxisLabel).toBe('CPU'); diff --git a/yarn.lock b/yarn.lock index 45767b9512f..21c5ea929d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -663,10 +663,10 @@ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.54.0.tgz#00320e845efd46716042cde0c348b990d4908daf" integrity sha512-DR17iy8TM5IbXEacqiDP0p8SuC/J8EL+98xbfVz5BKvRsPHpeZJQNlBF/petIV5d+KWM5A9v3GZTY7uMU7z/JQ== -"@gitlab/ui@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-2.2.0.tgz#8e384d3fb3d84f2886eacea75feb05e0ea42adcc" - integrity sha512-CCr1CjFyeycm1vrTtRKng5VknWWTN3fFw48YQThz/rgg0viVtA12oqz7oqGGAC+AktnWXtA/cxkXjVNpKTmEpA== +"@gitlab/ui@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-2.2.1.tgz#4b4f5c9234279a2ebeafc337c58eb6460aef963b" + integrity sha512-hxgZi1KRwd8EmVs1OeE/zcVH3GCE523GK4JWrTD8x9xPRS0O+NSIvbgqGBXp25UiG7jei9Up0X9BzZMTmZimug== dependencies: "@babel/standalone" "^7.0.0" bootstrap-vue "^2.0.0-rc.11" |