summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJose Vargas <jvargas@gitlab.com>2019-08-28 17:18:03 -0500
committerJose Vargas <jvargas@gitlab.com>2019-09-09 12:15:55 -0500
commitf21258035f3bdbdab002f4701ff0577a180dad56 (patch)
treebaed169a57403dce168c0bd4151ef3f180b70d86
parentfd515cca50a35f7f39d8dd134e69c1b275ba632f (diff)
downloadgitlab-ce-jivanvl-add-support-heatmap-charts.tar.gz
Add heatmap chart supportjivanvl-add-support-heatmap-charts
This adds the support to add heatmap charts to the monitoring dashboard, via the use of the custom dashboards feature
-rw-r--r--app/assets/javascripts/monitoring/components/charts/heatmap.vue114
-rw-r--r--app/assets/javascripts/monitoring/components/panel_type.vue7
-rw-r--r--changelogs/unreleased/jivanvl-add-support-heatmap-charts.yml5
-rw-r--r--spec/javascripts/monitoring/charts/heatmap_spec.js75
-rw-r--r--spec/javascripts/monitoring/mock_data.js79
5 files changed, 280 insertions, 0 deletions
diff --git a/app/assets/javascripts/monitoring/components/charts/heatmap.vue b/app/assets/javascripts/monitoring/components/charts/heatmap.vue
new file mode 100644
index 00000000000..c5d200c7488
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/charts/heatmap.vue
@@ -0,0 +1,114 @@
+<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>
diff --git a/app/assets/javascripts/monitoring/components/panel_type.vue b/app/assets/javascripts/monitoring/components/panel_type.vue
index 73ff651d510..0a783bd5290 100644
--- a/app/assets/javascripts/monitoring/components/panel_type.vue
+++ b/app/assets/javascripts/monitoring/components/panel_type.vue
@@ -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"
diff --git a/changelogs/unreleased/jivanvl-add-support-heatmap-charts.yml b/changelogs/unreleased/jivanvl-add-support-heatmap-charts.yml
new file mode 100644
index 00000000000..e9a13868559
--- /dev/null
+++ b/changelogs/unreleased/jivanvl-add-support-heatmap-charts.yml
@@ -0,0 +1,5 @@
+---
+title: Add heatmap chart support
+merge_request: 32424
+author:
+type: added
diff --git a/spec/javascripts/monitoring/charts/heatmap_spec.js b/spec/javascripts/monitoring/charts/heatmap_spec.js
new file mode 100644
index 00000000000..3cfaaf8e5ec
--- /dev/null
+++ b/spec/javascripts/monitoring/charts/heatmap_spec.js
@@ -0,0 +1,75 @@
+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);
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js
index 17e7314e214..175eb6365aa 100644
--- a/spec/javascripts/monitoring/mock_data.js
+++ b/spec/javascripts/monitoring/mock_data.js
@@ -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],
+ ],
+ },
+ ],
+ },
+ ],
+};