Commit 43e0ce80 authored by Fatih Acet's avatar Fatih Acet

Merge branch 'ee-jivanvl-add-support-heatmap-charts' into 'master'

Add heatmap chart support

See merge request gitlab-org/gitlab!16559
parents eadb7263 fcc2b0ea
<script>
import { GlHeatmap } from '@gitlab/ui/dist/charts';
import dateformat from 'dateformat';
import PrometheusHeader from '../shared/prometheus_header.vue';
import ResizableChartContainer from '~/vue_shared/components/resizable_chart/resizable_chart_container.vue';
import { graphDataValidatorForValues } from '../../utils';
export default {
components: {
GlHeatmap,
ResizableChartContainer,
PrometheusHeader,
},
props: {
graphData: {
type: Object,
required: true,
validator: graphDataValidatorForValues.bind(null, false),
},
containerWidth: {
type: Number,
required: true,
},
},
computed: {
chartData() {
return this.queries.result.reduce(
(acc, result, i) => [...acc, ...result.values.map((value, j) => [i, j, value[1]])],
[],
);
},
xAxisName() {
return this.graphData.x_label || '';
},
yAxisName() {
return this.graphData.y_label || '';
},
xAxisLabels() {
return this.queries.result.map(res => Object.values(res.metric)[0]);
},
yAxisLabels() {
return this.result.values.map(val => {
const [yLabel] = val;
return dateformat(new Date(yLabel), 'HH:MM:ss');
});
},
result() {
return this.queries.result[0];
},
queries() {
return this.graphData.queries[0];
},
},
};
</script>
<template>
<div class="prometheus-graph col-12 col-lg-6">
<prometheus-header :graph-title="graphData.title" />
<resizable-chart-container>
<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"
:width="containerWidth"
/>
</resizable-chart-container>
</div>
</template>
......@@ -13,6 +13,7 @@ import Icon from '~/vue_shared/components/icon.vue';
import MonitorTimeSeriesChart from './charts/time_series.vue';
import MonitorAnomalyChart from './charts/anomaly.vue';
import MonitorSingleStatChart from './charts/single_stat.vue';
import MonitorHeatmapChart from './charts/heatmap.vue';
import MonitorEmptyChart from './charts/empty_chart.vue';
import TrackEventDirective from '~/vue_shared/directives/track_event';
import { downloadCSVOptions, generateLinkToChartOptions } from '../utils';
......@@ -20,6 +21,7 @@ import { downloadCSVOptions, generateLinkToChartOptions } from '../utils';
export default {
components: {
MonitorSingleStatChart,
MonitorHeatmapChart,
MonitorEmptyChart,
Icon,
GlDropdown,
......@@ -99,6 +101,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"
/>
<component
:is="monitorChartComponent"
v-else-if="graphDataHasMetrics"
......
<script>
export default {
props: {
graphTitle: {
type: String,
required: true,
},
},
};
</script>
<template>
<div class="prometheus-graph-header">
<h5 class="prometheus-graph-title js-graph-title">{{ graphTitle }}</h5>
</div>
</template>
---
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];
expect(row.length).toBe(3);
expect(heatmapChart.vm.chartData.length).toBe(30);
});
it('returns a series of labels for the x axis', () => {
const { xAxisLabels } = heatmapChart.vm;
expect(xAxisLabels.length).toBe(5);
});
it('returns a series of labels for the y axis', () => {
const { yAxisLabels } = heatmapChart.vm;
expect(yAxisLabels.length).toBe(6);
});
});
});
});
......@@ -1013,3 +1013,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],
],
},
],
},
],
};
import { shallowMount } from '@vue/test-utils';
import PrometheusHeader from '~/monitoring/components/shared/prometheus_header.vue';
describe('Prometheus Header component', () => {
let prometheusHeader;
beforeEach(() => {
prometheusHeader = shallowMount(PrometheusHeader, {
propsData: {
graphTitle: 'graph header',
},
});
});
afterEach(() => {
prometheusHeader.destroy();
});
describe('Prometheus header component', () => {
it('should show a title', () => {
const title = prometheusHeader.vm.$el.querySelector('.js-graph-title').textContent;
expect(title).toBe('graph header');
});
});
});
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