Коммит f2125803 создал по автору Jose Vargas's avatar Jose Vargas
Просмотр файлов

Add heatmap chart support

This adds the support to add heatmap charts to the monitoring
dashboard, via the use of the custom dashboards feature
владелец fd515cca
<script>
import { GlHeatmap } from '@gitlab/ui/dist/charts';
import dateformat from 'dateformat';
import { debounceByAnimationFrame } from '~/lib/utils/common_utils';
import { chartHeight } from '../../constants';
import { graphDataValidatorForValues } from '../../utils';
export default {
components: {
GlHeatmap,
},
props: {
graphData: {
type: Object,
required: true,
validator: graphDataValidatorForValues.bind(null, false),
},
containerWidth: {
type: Number,
required: true,
},
},
data() {
return {
debouncedResize: {},
height: chartHeight,
width: 0,
};
},
computed: {
chartData() {
const [queries] = this.graphData.queries;
const yDim = queries.result[0].values.length;
const data = [];
for (let j = 0; j < yDim; j += 1) {
for (let i = 0; i < queries.result.length; i += 1) {
const value = queries.result[i].values[j];
data.push([i, j, value[1]]);
}
}
return data;
},
xAxisName() {
const xLabel = this.graphData.x_label;
return xLabel != null ? xLabel : '';
},
yAxisName() {
const yLabel = this.graphData.y_label;
return yLabel != null ? yLabel : '';
},
xAxisLabels() {
const [queries] = this.graphData.queries;
const axisLabels = queries.result.reduce((acc, res) => {
const [keyMetric] = Object.keys(res.metric);
const keyValue = res.metric[keyMetric];
return acc.concat(keyValue);
}, []);
return axisLabels;
},
yAxisLabels() {
const [queries] = this.graphData.queries;
const axisLabels = queries.result[0].values.reduce((acc, val) => {
const [yLabel] = val;
const convertedDate = new Date(yLabel);
return acc.concat(dateformat(convertedDate, 'HH:MM:ss'));
}, []);
return axisLabels;
},
},
watch: {
containerWidth: 'onResize',
},
beforeDestroy() {
window.removeEventListener('resize', this.debouncedResize);
},
created() {
this.debouncedResize = debounceByAnimationFrame(this.onResize);
window.addEventListener('resize', this.debouncedResize);
},
methods: {
onResize() {
if (!this.$refs.heatmap) return;
const { width } = this.$refs.heatmap.$el.getBoundingClientRect();
this.width = width;
},
},
};
</script>
<template>
<div class="prometheus-graph col-12 col-lg-6">
<div class="prometheus-graph-header">
<h5 class="prometheus-graph-title js-graph-title">{{ graphData.title }}</h5>
</div>
<gl-heatmap
ref="heatmapChart"
v-bind="$attrs"
:data-series="chartData"
:x-axis-name="xAxisName"
:y-axis-name="yAxisName"
:x-axis-labels="xAxisLabels"
:y-axis-labels="yAxisLabels"
:height="height"
:width="width"
/>
</div>
</template>
......@@ -12,12 +12,14 @@ import {
import Icon from '~/vue_shared/components/icon.vue';
import MonitorTimeSeriesChart from './charts/time_series.vue';
import MonitorSingleStatChart from './charts/single_stat.vue';
import MonitorHeatmapChart from './charts/heatmap.vue';
import MonitorEmptyChart from './charts/empty_chart.vue';
export default {
components: {
MonitorSingleStatChart,
MonitorTimeSeriesChart,
MonitorHeatmapChart,
MonitorEmptyChart,
Icon,
GlDropdown,
......@@ -92,6 +94,11 @@ export default {
v-if="isPanelType('single-stat') && graphDataHasMetrics"
:graph-data="graphData"
/>
<monitor-heatmap-chart
v-else-if="isPanelType('heatmap') && graphDataHasMetrics"
:graph-data="graphData"
:container-width="dashboardWidth"
/>
<monitor-time-series-chart
v-else-if="graphDataHasMetrics"
:graph-data="graphData"
......
---
title: Add heatmap chart support
merge_request: 32424
author:
type: added
import { shallowMount } from '@vue/test-utils';
import { GlHeatmap } from '@gitlab/ui/dist/charts';
import Heatmap from '~/monitoring/components/charts/heatmap.vue';
import { graphDataPrometheusQueryRangeMultiTrack } from '../mock_data';
describe('Heatmap component', () => {
let heatmapChart;
let store;
beforeEach(() => {
heatmapChart = shallowMount(Heatmap, {
propsData: {
graphData: graphDataPrometheusQueryRangeMultiTrack,
containerWidth: 100,
},
store,
});
});
afterEach(() => {
heatmapChart.destroy();
});
describe('wrapped components', () => {
describe('GitLab UI heatmap chart', () => {
let glHeatmapChart;
beforeEach(() => {
glHeatmapChart = heatmapChart.find(GlHeatmap);
});
it('is a Vue instance', () => {
expect(glHeatmapChart.isVueInstance()).toBe(true);
});
it('should display a label on the x axis', () => {
expect(heatmapChart.vm.xAxisName).toBe(graphDataPrometheusQueryRangeMultiTrack.x_label);
});
it('should display a label on the y axis', () => {
expect(heatmapChart.vm.yAxisName).toBe(graphDataPrometheusQueryRangeMultiTrack.y_label);
});
// According to the echarts docs https://echarts.apache.org/en/option.html#series-heatmap.data
// each row of the heatmap chart is represented by an array inside another parent array
// e.g. [[0, 0, 10]], the format represents the column, the row and finally the value
// corresponding to the cell
it('should return chartData with a length of x by y, with a length of 3 per array', () => {
const row = heatmapChart.vm.chartData[0];
const queryResult = graphDataPrometheusQueryRangeMultiTrack.queries[0].result;
const xDim = queryResult.length;
const yDim = queryResult[0].values.length;
const totalLengthArray = xDim * yDim;
expect(row.length).toBe(3);
expect(heatmapChart.vm.chartData.length).toBe(totalLengthArray);
});
it('returns a series of labels for the x axis', () => {
const { xAxisLabels } = heatmapChart.vm;
const queryResult = graphDataPrometheusQueryRangeMultiTrack.queries[0].result;
expect(xAxisLabels.length).toBe(queryResult.length);
});
it('returns a series of labels for the y axis', () => {
const { yAxisLabels } = heatmapChart.vm;
const queryResult = graphDataPrometheusQueryRangeMultiTrack.queries[0].result;
expect(yAxisLabels.length).toBe(queryResult[0].values.length);
});
});
});
});
......@@ -1009,3 +1009,82 @@ export const graphDataPrometheusQueryRange = {
},
],
};
export const graphDataPrometheusQueryRangeMultiTrack = {
title: 'Super Chart A3',
type: 'heatmap',
weight: 3,
x_label: 'Status Code',
y_label: 'Time',
metrics: [],
queries: [
{
metricId: '1',
id: 'response_metrics_nginx_ingress_throughput_status_code',
query_range:
'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[60m])) by (status_code)',
unit: 'req / sec',
label: 'Status Code',
metric_id: 1,
prometheus_endpoint_path:
'/root/rails_nodb/environments/3/prometheus/api/v1/query_range?query=sum%28rate%28nginx_upstream_responses_total%7Bupstream%3D~%22%25%7Bkube_namespace%7D-%25%7Bci_environment_slug%7D-.%2A%22%7D%5B2m%5D%29%29+by+%28status_code%29',
result: [
{
metric: { status_code: '1xx' },
values: [
['2019-08-30T15:00:00.000Z', 0],
['2019-08-30T16:00:00.000Z', 2],
['2019-08-30T17:00:00.000Z', 0],
['2019-08-30T18:00:00.000Z', 0],
['2019-08-30T19:00:00.000Z', 0],
['2019-08-30T20:00:00.000Z', 3],
],
},
{
metric: { status_code: '2xx' },
values: [
['2019-08-30T15:00:00.000Z', 1],
['2019-08-30T16:00:00.000Z', 3],
['2019-08-30T17:00:00.000Z', 6],
['2019-08-30T18:00:00.000Z', 10],
['2019-08-30T19:00:00.000Z', 8],
['2019-08-30T20:00:00.000Z', 6],
],
},
{
metric: { status_code: '3xx' },
values: [
['2019-08-30T15:00:00.000Z', 1],
['2019-08-30T16:00:00.000Z', 2],
['2019-08-30T17:00:00.000Z', 3],
['2019-08-30T18:00:00.000Z', 3],
['2019-08-30T19:00:00.000Z', 2],
['2019-08-30T20:00:00.000Z', 1],
],
},
{
metric: { status_code: '4xx' },
values: [
['2019-08-30T15:00:00.000Z', 2],
['2019-08-30T16:00:00.000Z', 0],
['2019-08-30T17:00:00.000Z', 0],
['2019-08-30T18:00:00.000Z', 2],
['2019-08-30T19:00:00.000Z', 0],
['2019-08-30T20:00:00.000Z', 2],
],
},
{
metric: { status_code: '5xx' },
values: [
['2019-08-30T15:00:00.000Z', 0],
['2019-08-30T16:00:00.000Z', 1],
['2019-08-30T17:00:00.000Z', 0],
['2019-08-30T18:00:00.000Z', 0],
['2019-08-30T19:00:00.000Z', 0],
['2019-08-30T20:00:00.000Z', 2],
],
},
],
},
],
};
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать