import { isFinite, uniq, sortBy, includes } from 'lodash'; import { SUPPORTED_FORMATS, getFormatter } from '~/lib/utils/unit_format'; import { __, s__ } from '~/locale'; import { thresholdModeTypes } from '../../constants'; import { formatDate, timezones, formats } from '../../format_date'; const yAxisBoundaryGap = [0.1, 0.1]; /** * Max string length of formatted axis tick */ const maxDataAxisTickLength = 8; // Defaults const defaultFormat = SUPPORTED_FORMATS.engineering; const defaultYAxisFormat = defaultFormat; const defaultYAxisPrecision = 2; const defaultTooltipFormat = defaultFormat; const defaultTooltipPrecision = 3; // Give enough space for y-axis with units and name. const chartGridLeft = 63; // larger gap than gitlab-ui's default to fit formatted numbers const chartGridRight = 10; // half of the scroll-handle icon for data zoom const yAxisNameGap = chartGridLeft - 12; // offset the axis label line-height // Axis options /** * Axis types * @see https://echarts.apache.org/en/option.html#xAxis.type */ export const axisTypes = { /** * Category axis, suitable for discrete category data. */ category: 'category', /** * Time axis, suitable for continuous time series data. */ time: 'time', }; /** * 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); // default to engineeringNotation, same as gitlab-ui 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: yAxisNameGap, scale: true, boundaryGap: yAxisBoundaryGap, ...getDataAxisOptions({ name, format, precision, }), }; }; export const getTimeAxisOptions = ({ timezone = timezones.LOCAL, format = formats.shortDateTime, } = {}) => ({ name: __('Time'), type: axisTypes.time, axisLabel: { formatter: (date) => formatDate(date, { format, timezone }), }, axisPointer: { snap: false, }, }); // Chart grid /** * Grid with enough room to display chart. */ export const getChartGrid = ({ left = chartGridLeft, right = chartGridRight } = {}) => ({ left, right, }); // Tooltip options export const getTooltipFormatter = ({ format = defaultTooltipFormat, precision = defaultTooltipPrecision, } = {}) => { const formatter = getFormatter(format); return (num) => formatter(num, precision); }; // Thresholds /** * * Used to find valid thresholds for the gauge chart * * An array of thresholds values is * - duplicate values are removed; * - filtered for invalid values; * - sorted in ascending order; * - only first two values are used. */ export const getValidThresholds = ({ mode, range = {}, values = [] } = {}) => { const supportedModes = [thresholdModeTypes.ABSOLUTE, thresholdModeTypes.PERCENTAGE]; const { min, max } = range; /** * return early if min and max have invalid values * or mode has invalid value */ if (!isFinite(min) || !isFinite(max) || min >= max || !includes(supportedModes, mode)) { return []; } const uniqueThresholds = uniq(values); const numberThresholds = uniqueThresholds.filter((threshold) => isFinite(threshold)); const validThresholds = numberThresholds.filter((threshold) => { let isValid; if (mode === thresholdModeTypes.PERCENTAGE) { isValid = threshold > 0 && threshold < 100; } else if (mode === thresholdModeTypes.ABSOLUTE) { isValid = threshold > min && threshold < max; } return isValid; }); const transformedThresholds = validThresholds.map((threshold) => { let transformedThreshold; if (mode === 'percentage') { transformedThreshold = (threshold / 100) * (max - min); } else { transformedThreshold = threshold; } return transformedThreshold; }); const sortedThresholds = sortBy(transformedThresholds); const reducedThresholdsArray = sortedThresholds.length > 2 ? [sortedThresholds[0], sortedThresholds[1]] : [...sortedThresholds]; return reducedThresholdsArray; };