"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.defaultColor = exports.MetricVis = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _react = _interopRequireWildcard(require("react")); var _i18n = require("@kbn/i18n"); var _charts = require("@elastic/charts"); var _utils = require("@kbn/visualizations-plugin/common/utils"); var _coloring = require("@kbn/coloring"); var _react2 = require("@emotion/react"); var _uiTheme = require("@kbn/ui-theme"); var _eui = require("@elastic/eui"); var _chartExpressionsCommon = require("@kbn/chart-expressions-common"); var _constants = require("../../common/constants"); var _services = require("../services"); var _utils2 = require("../utils"); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License * 2.0 and the Server Side Public License, v 1; you may not use this file except * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ const defaultColor = _uiTheme.euiThemeVars.euiColorLightestShade; exports.defaultColor = defaultColor; function enhanceFieldFormat(serializedFieldFormat) { var _serializedFieldForma; const formatId = (serializedFieldFormat === null || serializedFieldFormat === void 0 ? void 0 : serializedFieldFormat.id) || 'number'; if (formatId === 'duration' && !(serializedFieldFormat !== null && serializedFieldFormat !== void 0 && (_serializedFieldForma = serializedFieldFormat.params) !== null && _serializedFieldForma !== void 0 && _serializedFieldForma.formatOverride)) { return { ...serializedFieldFormat, params: { // by default use the compact precise format outputFormat: 'humanizePrecise', outputPrecision: 1, useShortSuffix: true, // but if user configured something else, use it ...serializedFieldFormat.params } }; } return serializedFieldFormat !== null && serializedFieldFormat !== void 0 ? serializedFieldFormat : { id: formatId }; } const getMetricFormatter = (accessor, columns) => { const serializedFieldFormat = (0, _utils.getFormatByAccessor)(accessor, columns); const enhancedFieldFormat = enhanceFieldFormat(serializedFieldFormat); return (0, _services.getFormatService)().deserialize(enhancedFieldFormat).getConverterFor('text'); }; const getColor = (value, paletteParams, accessors, data, rowNumber) => { var _getPaletteService$ge, _getPaletteService$ge2; const { min, max } = (0, _utils2.getDataBoundsForPalette)(accessors, data, rowNumber); return (_getPaletteService$ge = (0, _services.getPaletteService)().get(_coloring.CUSTOM_PALETTE)) === null || _getPaletteService$ge === void 0 ? void 0 : (_getPaletteService$ge2 = _getPaletteService$ge.getColorForValue) === null || _getPaletteService$ge2 === void 0 ? void 0 : _getPaletteService$ge2.call(_getPaletteService$ge, value, paletteParams, { min, max }); }; const buildFilterEvent = (rowIdx, columnIdx, table) => { const column = table.columns[columnIdx]; return { name: 'filter', data: { data: [{ table, column: columnIdx, row: rowIdx, value: table.rows[rowIdx][column.id] }] } }; }; const getIcon = type => ({ width, height, color }) => /*#__PURE__*/_react.default.createElement(_eui.EuiIcon, { type: type, width: width, height: height, fill: color, style: { width, height } }); const MetricVis = ({ data, config, renderComplete, fireEvent, renderMode, filterable, overrides }) => { var _getColumnByAccessor, _chartTheme$metric$mi, _chartTheme$metric; const chartTheme = (0, _services.getThemeService)().useChartsTheme(); const onRenderChange = (0, _react.useCallback)(isRendered => { if (isRendered) { // this requestAnimationFrame call is a temporary fix for https://github.com/elastic/elastic-charts/issues/2124 window.requestAnimationFrame(() => { renderComplete(); }); } }, [renderComplete]); const [scrollChildHeight, setScrollChildHeight] = (0, _react.useState)('100%'); const scrollContainerRef = (0, _react.useRef)(null); const scrollDimensions = (0, _eui.useResizeObserver)(scrollContainerRef.current); const baseTheme = (0, _services.getThemeService)().useChartsBaseTheme(); const primaryMetricColumn = (0, _utils.getColumnByAccessor)(config.dimensions.metric, data.columns); const formatPrimaryMetric = getMetricFormatter(config.dimensions.metric, data.columns); let secondaryMetricColumn; let formatSecondaryMetric; if (config.dimensions.secondaryMetric) { secondaryMetricColumn = (0, _utils.getColumnByAccessor)(config.dimensions.secondaryMetric, data.columns); formatSecondaryMetric = getMetricFormatter(config.dimensions.secondaryMetric, data.columns); } let breakdownByColumn; let formatBreakdownValue; if (config.dimensions.breakdownBy) { breakdownByColumn = (0, _utils.getColumnByAccessor)(config.dimensions.breakdownBy, data.columns); formatBreakdownValue = (0, _services.getFormatService)().deserialize((0, _utils.getFormatByAccessor)(config.dimensions.breakdownBy, data.columns)).getConverterFor('text'); } const maxColId = config.dimensions.max ? (_getColumnByAccessor = (0, _utils.getColumnByAccessor)(config.dimensions.max, data.columns)) === null || _getColumnByAccessor === void 0 ? void 0 : _getColumnByAccessor.id : undefined; const metricConfigs = (breakdownByColumn ? data.rows : data.rows.slice(0, 1)).map((row, rowIdx) => { var _config$metric$second, _secondaryMetricColum, _config$metric, _config$metric2, _getColor, _breakdownByColumn, _config$metric$color; const value = row[primaryMetricColumn.id] !== null ? row[primaryMetricColumn.id] : NaN; const title = breakdownByColumn ? formatBreakdownValue(row[breakdownByColumn.id]) : primaryMetricColumn.name; const subtitle = breakdownByColumn ? primaryMetricColumn.name : config.metric.subtitle; const secondaryPrefix = (_config$metric$second = config.metric.secondaryPrefix) !== null && _config$metric$second !== void 0 ? _config$metric$second : (_secondaryMetricColum = secondaryMetricColumn) === null || _secondaryMetricColum === void 0 ? void 0 : _secondaryMetricColum.name; const baseMetric = { value, valueFormatter: formatPrimaryMetric, title, subtitle, icon: (_config$metric = config.metric) !== null && _config$metric !== void 0 && _config$metric.icon ? getIcon((_config$metric2 = config.metric) === null || _config$metric2 === void 0 ? void 0 : _config$metric2.icon) : undefined, extra: /*#__PURE__*/_react.default.createElement("span", null, secondaryPrefix, secondaryMetricColumn ? `${secondaryPrefix ? ' ' : ''}${formatSecondaryMetric(row[secondaryMetricColumn.id])}` : undefined), color: config.metric.palette && value != null ? (_getColor = getColor(value, config.metric.palette, { metric: primaryMetricColumn.id, max: maxColId, breakdownBy: (_breakdownByColumn = breakdownByColumn) === null || _breakdownByColumn === void 0 ? void 0 : _breakdownByColumn.id }, data, rowIdx)) !== null && _getColor !== void 0 ? _getColor : defaultColor : (_config$metric$color = config.metric.color) !== null && _config$metric$color !== void 0 ? _config$metric$color : defaultColor }; const trendId = breakdownByColumn ? row[breakdownByColumn.id] : _constants.DEFAULT_TRENDLINE_NAME; if (config.metric.trends && config.metric.trends[trendId]) { const metricWTrend = { ...baseMetric, trend: config.metric.trends[trendId], trendShape: 'area', trendA11yTitle: _i18n.i18n.translate('expressionMetricVis.trendA11yTitle', { defaultMessage: '{dataTitle} over time.', values: { dataTitle: primaryMetricColumn.name } }), trendA11yDescription: _i18n.i18n.translate('expressionMetricVis.trendA11yDescription', { defaultMessage: 'A line chart showing the trend of the primary metric over time.' }) }; return metricWTrend; } if (maxColId && config.metric.progressDirection) { const metricWProgress = { ...baseMetric, domainMax: row[maxColId], progressBarDirection: config.metric.progressDirection }; return metricWProgress; } return baseMetric; }); if (config.metric.minTiles) { while (metricConfigs.length < config.metric.minTiles) { metricConfigs.push(undefined); } } const { metric: { maxCols } } = config; const numRows = metricConfigs.length / maxCols; const minHeight = (_chartTheme$metric$mi = (_chartTheme$metric = chartTheme.metric) === null || _chartTheme$metric === void 0 ? void 0 : _chartTheme$metric.minHeight) !== null && _chartTheme$metric$mi !== void 0 ? _chartTheme$metric$mi : baseTheme.metric.minHeight; (0, _react.useEffect)(() => { var _scrollDimensions$hei; const minimumRequiredVerticalSpace = minHeight * numRows; setScrollChildHeight(((_scrollDimensions$hei = scrollDimensions.height) !== null && _scrollDimensions$hei !== void 0 ? _scrollDimensions$hei : -Infinity) > minimumRequiredVerticalSpace ? '100%' : `${minimumRequiredVerticalSpace}px`); }, [numRows, minHeight, scrollDimensions.height]); const { theme: settingsThemeOverrides = {}, ...settingsOverrides } = (0, _chartExpressionsCommon.getOverridesFor)(overrides, 'settings'); const grid = []; for (let i = 0; i < metricConfigs.length; i += maxCols) { grid.push(metricConfigs.slice(i, i + maxCols)); } let pixelHeight; let pixelWidth; if (renderMode === 'edit') { var _grid$; // In the editor, we constrain the maximum size of the tiles for aesthetic reasons const maxTileSideLength = metricConfigs.flat().length > 1 ? 200 : 300; pixelHeight = grid.length * maxTileSideLength; pixelWidth = ((_grid$ = grid[0]) === null || _grid$ === void 0 ? void 0 : _grid$.length) * maxTileSideLength; } return /*#__PURE__*/_react.default.createElement("div", { ref: scrollContainerRef, css: (0, _react2.css)` height: ${pixelHeight ? `${pixelHeight}px` : '100%'}; width: ${pixelWidth ? `${pixelWidth}px` : '100%'}; max-height: 100%; max-width: 100%; overflow-y: auto; ${(0, _eui.useEuiScrollBar)()} ` }, /*#__PURE__*/_react.default.createElement("div", { css: (0, _react2.css)` height: ${scrollChildHeight}; ` }, /*#__PURE__*/_react.default.createElement(_charts.Chart, (0, _chartExpressionsCommon.getOverridesFor)(overrides, 'chart'), /*#__PURE__*/_react.default.createElement(_charts.Settings, (0, _extends2.default)({ theme: [{ background: { color: 'transparent' }, metric: { background: defaultColor, barBackground: _uiTheme.euiThemeVars.euiColorLightShade }, ...chartTheme }, ...(Array.isArray(settingsThemeOverrides) ? settingsThemeOverrides : [settingsThemeOverrides])], baseTheme: baseTheme, onRenderChange: onRenderChange, onElementClick: filterable ? events => { var _breakdownByColumn2; const colRef = (_breakdownByColumn2 = breakdownByColumn) !== null && _breakdownByColumn2 !== void 0 ? _breakdownByColumn2 : primaryMetricColumn; const rowLength = grid[0].length; events.forEach(event => { if ((0, _charts.isMetricElementEvent)(event)) { const colIdx = data.columns.findIndex(col => col === colRef); fireEvent(buildFilterEvent(event.rowIndex * rowLength + event.columnIndex, colIdx, data)); } }); } : undefined }, settingsOverrides)), /*#__PURE__*/_react.default.createElement(_charts.Metric, { id: "metric", data: grid })))); }; exports.MetricVis = MetricVis;