Commit a0a26f49 authored by Martin Wortschack's avatar Martin Wortschack

Merge branch '201999-define-formatter-dashboard-yml' into 'master'

Adds new properties to the dashboard definition to customize format

Closes #201999

See merge request gitlab-org/gitlab!25785
parents 7efa414b 364e813c
import { SUPPORTED_FORMATS, getFormatter } from '~/lib/utils/unit_format';
import { s__ } from '~/locale';
const yAxisBoundaryGap = [0.1, 0.1];
/**
* Max string length of formatted axis tick
*/
const maxDataAxisTickLength = 8;
// Defaults
const defaultFormat = SUPPORTED_FORMATS.number;
const defaultYAxisFormat = defaultFormat;
const defaultYAxisPrecision = 2;
const defaultTooltipFormat = defaultFormat;
const defaultTooltipPrecision = 3;
// Give enough space for y-axis with units and name.
const chartGridLeft = 75;
// Axis options
/**
* Converts .yml parameters to echarts axis options for data axis
* @param {Object} param - Dashboard .yml definition options
*/
const getDataAxisOptions = ({ format, precision, name }) => {
const formatter = getFormatter(format);
return {
name,
nameLocation: 'center', // same as gitlab-ui's default
scale: true,
axisLabel: {
formatter: val => formatter(val, precision, maxDataAxisTickLength),
},
};
};
/**
* Converts .yml parameters to echarts y-axis options
* @param {Object} param - Dashboard .yml definition options
*/
export const getYAxisOptions = ({
name = s__('Metrics|Values'),
format = defaultYAxisFormat,
precision = defaultYAxisPrecision,
} = {}) => {
return {
nameGap: 63, // larger gap than gitlab-ui's default to fit with formatted numbers
scale: true,
boundaryGap: yAxisBoundaryGap,
...getDataAxisOptions({
name,
format,
precision,
}),
};
};
// Chart grid
/**
* Grid with enough room to display chart.
*/
export const getChartGrid = ({ left = chartGridLeft } = {}) => ({ left });
// Tooltip options
export const getTooltipFormatter = ({
format = defaultTooltipFormat,
precision = defaultTooltipPrecision,
} = {}) => {
const formatter = getFormatter(format);
return num => formatter(num, precision);
};
......@@ -4,7 +4,6 @@ import { GlLink, GlButton, GlTooltip, GlResizeObserverDirective } from '@gitlab/
import { GlAreaChart, GlLineChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
import dateFormat from 'dateformat';
import { s__, __ } from '~/locale';
import { getFormatter } from '~/lib/utils/unit_format';
import { getSvgIconPathContent } from '~/lib/utils/icon_utils';
import Icon from '~/vue_shared/components/icon.vue';
import {
......@@ -16,6 +15,7 @@ import {
dateFormats,
chartColorValues,
} from '../../constants';
import { getYAxisOptions, getChartGrid, getTooltipFormatter } from './options';
import { makeDataSeries } from '~/helpers/monitor_helper';
import { graphDataValidatorForValues } from '../../utils';
......@@ -30,15 +30,13 @@ const deploymentYAxisCoords = {
max: 100,
};
const THROTTLED_DATAZOOM_WAIT = 1000; // miliseconds
const THROTTLED_DATAZOOM_WAIT = 1000; // milliseconds
const timestampToISODate = timestamp => new Date(timestamp).toISOString();
const events = {
datazoom: 'datazoom',
};
const yValFormatter = getFormatter('number');
export default {
components: {
GlAreaChart,
......@@ -167,14 +165,7 @@ export default {
const option = omit(this.option, ['series', 'yAxis', 'xAxis']);
const dataYAxis = {
name: this.yAxisLabel,
nameGap: 50, // same as gitlab-ui's default
nameLocation: 'center', // same as gitlab-ui's default
boundaryGap: [0.1, 0.1],
scale: true,
axisLabel: {
formatter: num => yValFormatter(num, 3),
},
...getYAxisOptions(this.graphData.yAxis),
...yAxis,
};
......@@ -204,6 +195,7 @@ export default {
series: this.chartOptionSeries,
xAxis: timeXAxis,
yAxis: [dataYAxis, deploymentsYAxis],
grid: getChartGrid(),
dataZoom: [this.dataZoomConfig],
...option,
};
......@@ -282,8 +274,9 @@ export default {
},
};
},
yAxisLabel() {
return `${this.graphData.y_label}`;
tooltipYFormatter() {
// Use same format as y-axis
return getTooltipFormatter({ format: this.graphData.yAxis?.format });
},
},
created() {
......@@ -315,12 +308,11 @@ export default {
this.tooltip.commitUrl = deploy.commitUrl;
} else {
const { seriesName, color, dataIndex } = dataPoint;
const value = yValFormatter(yVal, 3);
this.tooltip.content.push({
name: seriesName,
dataIndex,
value,
value: this.tooltipYFormatter(yVal),
color,
});
}
......
import { slugify } from '~/lib/utils/text_utility';
import createGqClient, { fetchPolicies } from '~/lib/graphql';
import { SUPPORTED_FORMATS } from '~/lib/utils/unit_format';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
export const gqClient = createGqClient(
......@@ -74,18 +75,38 @@ const mapToMetricsViewModel = (metrics, defaultLabel) =>
...metric,
}));
/**
* Maps an axis view model
*
* Defaults to a 2 digit precision and `number` format. It only allows
* formats in the SUPPORTED_FORMATS array.
*
* @param {Object} axis
*/
const mapToAxisViewModel = ({ name = '', format = SUPPORTED_FORMATS.number, precision = 2 }) => {
return {
name,
format: SUPPORTED_FORMATS[format] || SUPPORTED_FORMATS.number,
precision,
};
};
/**
* Maps a metrics panel to its view model
*
* @param {Object} panel - Metrics panel
* @returns {Object}
*/
const mapToPanelViewModel = ({ title = '', type, y_label, metrics = [] }) => {
const mapToPanelViewModel = ({ title = '', type, y_label, y_axis = {}, metrics = [] }) => {
// Both `y_axis.name` and `y_label` are supported for now
// https://gitlab.com/gitlab-org/gitlab/issues/208385
const yAxis = mapToAxisViewModel({ name: y_label, ...y_axis }); // eslint-disable-line babel/camelcase
return {
title,
type,
y_label,
metrics: mapToMetricsViewModel(metrics, y_label),
y_label: yAxis.name, // Changing y_label to yLabel is pending https://gitlab.com/gitlab-org/gitlab/issues/207198
yAxis,
metrics: mapToMetricsViewModel(metrics, yAxis.name),
};
};
......
---
title: Add properties to the dashboard definition to customize y-axis format
merge_request: 25785
author:
type: added
......@@ -12462,6 +12462,9 @@ msgstr ""
msgid "Metrics|Validating query"
msgstr ""
msgid "Metrics|Values"
msgstr ""
msgid "Metrics|View logs"
msgstr ""
......
import { SUPPORTED_FORMATS } from '~/lib/utils/unit_format';
import { getYAxisOptions, getTooltipFormatter } from '~/monitoring/components/charts/options';
describe('options spec', () => {
describe('getYAxisOptions', () => {
it('default options', () => {
const options = getYAxisOptions();
expect(options).toMatchObject({
name: expect.any(String),
axisLabel: {
formatter: expect.any(Function),
},
scale: true,
boundaryGap: [expect.any(Number), expect.any(Number)],
});
expect(options.name).not.toHaveLength(0);
});
it('name options', () => {
const yAxisName = 'My axis values';
const options = getYAxisOptions({
name: yAxisName,
});
expect(options).toMatchObject({
name: yAxisName,
nameLocation: 'center',
nameGap: expect.any(Number),
});
});
it('formatter options', () => {
const options = getYAxisOptions({
format: SUPPORTED_FORMATS.bytes,
});
expect(options.axisLabel.formatter).toEqual(expect.any(Function));
expect(options.axisLabel.formatter(1)).toBe('1.00B');
});
});
describe('getTooltipFormatter', () => {
it('default format', () => {
const formatter = getTooltipFormatter();
expect(formatter).toEqual(expect.any(Function));
expect(formatter(1)).toBe('1.000');
});
it('defined format', () => {
const formatter = getTooltipFormatter({
format: SUPPORTED_FORMATS.bytes,
});
expect(formatter(1)).toBe('1.000B');
});
});
});
......@@ -190,7 +190,8 @@ describe('Time series component', () => {
it('formats tooltip content', () => {
const name = 'Total';
const value = '5.556';
const value = '5.556MB';
const dataIndex = 0;
const seriesLabel = timeSeriesChart.find(GlChartSeriesLabel);
......@@ -348,9 +349,9 @@ describe('Time series component', () => {
});
});
it('additional y axis data', () => {
it('additional y-axis data', () => {
const mockCustomYAxisOption = {
name: 'Custom y axis label',
name: 'Custom y-axis label',
axisLabel: {
formatter: jest.fn(),
},
......@@ -397,8 +398,8 @@ describe('Time series component', () => {
deploymentFormatter = getChartOptions().yAxis[1].axisLabel.formatter;
});
it('rounds to 3 decimal places', () => {
expect(dataFormatter(0.88888)).toBe('0.889');
it('formats and rounds to 2 decimal places', () => {
expect(dataFormatter(0.88888)).toBe('0.89MB');
});
it('deployment formatter is set as is required to display a tooltip', () => {
......@@ -421,7 +422,7 @@ describe('Time series component', () => {
});
describe('yAxisLabel', () => {
it('y axis is configured correctly', () => {
it('y-axis is configured correctly', () => {
const { yAxis } = getChartOptions();
expect(yAxis).toHaveLength(2);
......
......@@ -393,13 +393,16 @@ export const metricsDashboardPayload = {
type: 'area-chart',
y_label: 'Total Memory Used',
weight: 4,
y_axis: {
format: 'megabytes',
},
metrics: [
{
id: 'system_metrics_kubernetes_container_memory_total',
query_range:
'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024',
'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1000/1000',
label: 'Total',
unit: 'GB',
unit: 'MB',
metric_id: 12,
prometheus_endpoint_path: 'http://test',
},
......
import { SUPPORTED_FORMATS } from '~/lib/utils/unit_format';
import {
uniqMetricsId,
parseEnvironmentsResponse,
......@@ -44,6 +45,11 @@ describe('mapToDashboardViewModel', () => {
title: 'Title A',
type: 'chart-type',
y_label: 'Y Label A',
yAxis: {
name: 'Y Label A',
format: 'number',
precision: 2,
},
metrics: [],
},
],
......@@ -90,6 +96,98 @@ describe('mapToDashboardViewModel', () => {
});
});
describe('panel mapping', () => {
const panelTitle = 'Panel Title';
const yAxisName = 'Y Axis Name';
let dashboard;
const setupWithPanel = panel => {
dashboard = {
panel_groups: [
{
panels: [panel],
},
],
};
};
const getMappedPanel = () => mapToDashboardViewModel(dashboard).panelGroups[0].panels[0];
it('group y_axis defaults', () => {
setupWithPanel({
title: panelTitle,
});
expect(getMappedPanel()).toEqual({
title: panelTitle,
y_label: '',
yAxis: {
name: '',
format: SUPPORTED_FORMATS.number,
precision: 2,
},
metrics: [],
});
});
it('panel with y_axis.name', () => {
setupWithPanel({
y_axis: {
name: yAxisName,
},
});
expect(getMappedPanel().y_label).toBe(yAxisName);
expect(getMappedPanel().yAxis.name).toBe(yAxisName);
});
it('panel with y_axis.name and y_label, displays y_axis.name', () => {
setupWithPanel({
y_label: 'Ignored Y Label',
y_axis: {
name: yAxisName,
},
});
expect(getMappedPanel().y_label).toBe(yAxisName);
expect(getMappedPanel().yAxis.name).toBe(yAxisName);
});
it('group y_label', () => {
setupWithPanel({
y_label: yAxisName,
});
expect(getMappedPanel().y_label).toBe(yAxisName);
expect(getMappedPanel().yAxis.name).toBe(yAxisName);
});
it('group y_axis format and precision', () => {
setupWithPanel({
title: panelTitle,
y_axis: {
precision: 0,
format: SUPPORTED_FORMATS.bytes,
},
});
expect(getMappedPanel().yAxis.format).toBe(SUPPORTED_FORMATS.bytes);
expect(getMappedPanel().yAxis.precision).toBe(0);
});
it('group y_axis unsupported format defaults to number', () => {
setupWithPanel({
title: panelTitle,
y_axis: {
format: 'invalid_format',
},
});
expect(getMappedPanel().yAxis.format).toBe(SUPPORTED_FORMATS.number);
});
});
describe('metrics mapping', () => {
const defaultLabel = 'Panel Label';
const dashboardWithMetric = (metric, label = defaultLabel) => ({
......
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