"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.DatatableComponent = exports.DataContext = exports.DEFAULT_PAGE_SIZE = void 0; require("./table_basic.scss"); var _coloring = require("@kbn/coloring"); var _react = _interopRequireWildcard(require("react")); var _i18n = require("@kbn/i18n"); var _useDeepCompareEffect = _interopRequireDefault(require("react-use/lib/useDeepCompareEffect")); var _eui = require("@elastic/eui"); var _public = require("@kbn/charts-plugin/public"); var _chartIcons = require("@kbn/chart-icons"); var _visualization_container = require("../../../visualization_container"); var _shared_components = require("../../../shared_components"); var _columns = require("./columns"); var _cell_value = require("./cell_value"); var _table_actions = require("./table_actions"); var _summary = require("../../../../common/expressions/datatable/summary"); var _transpose_helpers = require("../../../../common/expressions/datatable/transpose_helpers"); 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; you may not use this file except in compliance with the Elastic License * 2.0. */ const DataContext = /*#__PURE__*/_react.default.createContext({}); exports.DataContext = DataContext; const gridStyle = { border: 'horizontal', header: 'underline' }; const DEFAULT_PAGE_SIZE = 10; exports.DEFAULT_PAGE_SIZE = DEFAULT_PAGE_SIZE; const PAGE_SIZE_OPTIONS = [DEFAULT_PAGE_SIZE, 20, 30, 50, 100]; const DatatableComponent = props => { var _props$rowHasRowClick, _props$args$headerRow, _props$args$headerRow2; const dataGridRef = (0, _react.useRef)(null); const isInteractive = props.interactive; const [columnConfig, setColumnConfig] = (0, _react.useState)({ columns: props.args.columns, sortingColumnId: props.args.sortingColumnId, sortingDirection: props.args.sortingDirection }); const [firstLocalTable, updateTable] = (0, _react.useState)(props.data); // ** Pagination config const [pagination, setPagination] = (0, _react.useState)(undefined); (0, _react.useLayoutEffect)(() => { // Temporary solution: DataGrid should provide onRender callback setTimeout(() => { props.renderComplete(); }, 300); }, [props]); (0, _react.useEffect)(() => { var _props$args$pageSize; setPagination(props.args.pageSize ? { pageIndex: 0, pageSize: (_props$args$pageSize = props.args.pageSize) !== null && _props$args$pageSize !== void 0 ? _props$args$pageSize : DEFAULT_PAGE_SIZE } : undefined); }, [props.args.pageSize]); (0, _useDeepCompareEffect.default)(() => { setColumnConfig({ columns: props.args.columns, sortingColumnId: props.args.sortingColumnId, sortingDirection: props.args.sortingDirection }); }, [props.args.columns, props.args.sortingColumnId, props.args.sortingDirection]); (0, _useDeepCompareEffect.default)(() => { updateTable(props.data); }, [props.data]); const firstTableRef = (0, _react.useRef)(firstLocalTable); firstTableRef.current = firstLocalTable; (0, _react.useEffect)(() => { if (!(pagination !== null && pagination !== void 0 && pagination.pageIndex) && !(pagination !== null && pagination !== void 0 && pagination.pageSize)) return; const lastPageIndex = firstLocalTable.rows.length ? Math.ceil(firstLocalTable.rows.length / pagination.pageSize) - 1 : 0; /** * When the underlying data changes, there might be a case when actual pagination page * doesn't exist anymore - if the number of rows has decreased. * Set the last page as an actual. */ setPagination(pag => { if (!pag) { return pag; } return { pageIndex: pag.pageIndex > lastPageIndex ? lastPageIndex : pag.pageIndex, pageSize: pag.pageSize }; }); }, [pagination === null || pagination === void 0 ? void 0 : pagination.pageIndex, pagination === null || pagination === void 0 ? void 0 : pagination.pageSize, firstLocalTable.rows.length]); const untransposedDataRef = (0, _react.useRef)(props.untransposedData); untransposedDataRef.current = props.untransposedData; const hasAtLeastOneRowClickAction = (_props$rowHasRowClick = props.rowHasRowClickTriggerActions) === null || _props$rowHasRowClick === void 0 ? void 0 : _props$rowHasRowClick.some(x => x); const { getType, dispatchEvent, renderMode, formatFactory } = props; const formatters = (0, _react.useMemo)(() => firstLocalTable.columns.reduce((map, column) => { var _column$meta; return { ...map, [column.id]: formatFactory((_column$meta = column.meta) === null || _column$meta === void 0 ? void 0 : _column$meta.params) }; }, {}), [firstLocalTable, formatFactory]); const onClickValue = (0, _react.useCallback)(data => { dispatchEvent({ name: 'filter', data }); }, [dispatchEvent]); const onEditAction = (0, _react.useCallback)(data => { dispatchEvent({ name: 'edit', data }); }, [dispatchEvent]); const onChangeItemsPerPage = (0, _react.useCallback)(pageSize => onEditAction({ action: 'pagesize', size: pageSize }), [onEditAction]); // active page isn't persisted, so we manage this state locally const onChangePage = (0, _react.useCallback)(pageIndex => { setPagination(_pagination => { if (_pagination) { return { pageSize: _pagination === null || _pagination === void 0 ? void 0 : _pagination.pageSize, pageIndex }; } }); }, [setPagination]); const onRowContextMenuClick = (0, _react.useCallback)(data => { dispatchEvent({ name: 'tableRowContextMenuClick', data }); }, [dispatchEvent]); const handleFilterClick = (0, _react.useMemo)(() => isInteractive ? (0, _table_actions.createGridFilterHandler)(firstTableRef, onClickValue) : undefined, [firstTableRef, onClickValue, isInteractive]); const columnCellValueActions = (0, _react.useMemo)(() => isInteractive ? props.columnCellValueActions : undefined, [props.columnCellValueActions, isInteractive]); const handleTransposedColumnClick = (0, _react.useMemo)(() => isInteractive ? (0, _table_actions.createTransposeColumnFilterHandler)(onClickValue, untransposedDataRef) : undefined, [onClickValue, untransposedDataRef, isInteractive]); const bucketColumns = (0, _react.useMemo)(() => columnConfig.columns.filter((_col, index) => { var _col$meta, _col$meta$sourceParam, _getType; const col = firstTableRef.current.columns[index]; return (col === null || col === void 0 ? void 0 : (_col$meta = col.meta) === null || _col$meta === void 0 ? void 0 : (_col$meta$sourceParam = _col$meta.sourceParams) === null || _col$meta$sourceParam === void 0 ? void 0 : _col$meta$sourceParam.type) && ((_getType = getType(col.meta.sourceParams.type)) === null || _getType === void 0 ? void 0 : _getType.type) === 'buckets'; }).map(col => col.columnId), [firstTableRef, columnConfig, getType]); const isEmpty = firstLocalTable.rows.length === 0 || bucketColumns.length && props.data.rows.every(row => bucketColumns.every(col => row[col] == null)); const visibleColumns = (0, _react.useMemo)(() => columnConfig.columns.filter(col => !!col.columnId && !col.hidden).map(col => col.columnId), [columnConfig]); const { sortingColumnId: sortBy, sortingDirection: sortDirection } = props.args; const isReadOnlySorted = renderMode !== 'edit'; const onColumnResize = (0, _react.useMemo)(() => (0, _table_actions.createGridResizeHandler)(columnConfig, setColumnConfig, onEditAction), [onEditAction, setColumnConfig, columnConfig]); const onColumnHide = (0, _react.useMemo)(() => isInteractive ? (0, _table_actions.createGridHideHandler)(columnConfig, setColumnConfig, onEditAction) : undefined, [onEditAction, setColumnConfig, columnConfig, isInteractive]); const isNumericMap = (0, _react.useMemo)(() => { const numericMap = {}; for (const column of firstLocalTable.columns) { var _firstLocalTable$rows; // filtered metrics result as "number" type, but have no field numericMap[column.id] = column.meta.type === 'number' && column.meta.field != null || // as fallback check the first available value type // mind here: date can be seen as numbers, to carefully check that is a filtered metric column.meta.field == null && typeof ((_firstLocalTable$rows = firstLocalTable.rows.find(row => row[column.id] != null)) === null || _firstLocalTable$rows === void 0 ? void 0 : _firstLocalTable$rows[column.id]) === 'number'; } return numericMap; }, [firstLocalTable]); const alignments = (0, _react.useMemo)(() => { const alignmentMap = {}; columnConfig.columns.forEach(column => { if (column.alignment) { alignmentMap[column.columnId] = column.alignment; } else { alignmentMap[column.columnId] = isNumericMap[column.columnId] ? 'right' : 'left'; } }); return alignmentMap; }, [columnConfig, isNumericMap]); const minMaxByColumnId = (0, _react.useMemo)(() => { return (0, _shared_components.findMinMaxByColumnId)(columnConfig.columns.filter(({ columnId }) => isNumericMap[columnId]).map(({ columnId }) => columnId), props.data, _transpose_helpers.getOriginalId); }, [props.data, isNumericMap, columnConfig]); const headerRowHeight = (_props$args$headerRow = props.args.headerRowHeight) !== null && _props$args$headerRow !== void 0 ? _props$args$headerRow : 'single'; const headerRowLines = (_props$args$headerRow2 = props.args.headerRowHeightLines) !== null && _props$args$headerRow2 !== void 0 ? _props$args$headerRow2 : 1; const columns = (0, _react.useMemo)(() => { var _dataGridRef$current; return (0, _columns.createGridColumns)(bucketColumns, firstLocalTable, handleFilterClick, handleTransposedColumnClick, isReadOnlySorted, columnConfig, visibleColumns, formatFactory, onColumnResize, onColumnHide, alignments, headerRowHeight, headerRowLines, columnCellValueActions, (_dataGridRef$current = dataGridRef.current) === null || _dataGridRef$current === void 0 ? void 0 : _dataGridRef$current.closeCellPopover, props.columnFilterable); }, [bucketColumns, firstLocalTable, handleFilterClick, handleTransposedColumnClick, isReadOnlySorted, columnConfig, visibleColumns, formatFactory, onColumnResize, onColumnHide, alignments, headerRowHeight, headerRowLines, columnCellValueActions, props.columnFilterable]); const trailingControlColumns = (0, _react.useMemo)(() => { if (!hasAtLeastOneRowClickAction || !onRowContextMenuClick || !isInteractive) { return []; } return [{ headerCellRender: () => null, width: 40, id: 'trailingControlColumn', rowCellRender: function RowCellRender({ rowIndex }) { const { rowHasRowClickTriggerActions } = (0, _react.useContext)(DataContext); return /*#__PURE__*/_react.default.createElement(_eui.EuiButtonIcon, { "aria-label": _i18n.i18n.translate('xpack.lens.table.actionsLabel', { defaultMessage: 'Show actions' }), iconType: !!rowHasRowClickTriggerActions && !rowHasRowClickTriggerActions[rowIndex] ? 'empty' : 'boxesVertical', color: "text", onClick: () => { onRowContextMenuClick({ rowIndex, table: firstTableRef.current, columns: columnConfig.columns.map(col => col.columnId) }); } }); } }]; }, [firstTableRef, onRowContextMenuClick, columnConfig, hasAtLeastOneRowClickAction, isInteractive]); const renderCellValue = (0, _react.useMemo)(() => (0, _cell_value.createGridCell)(formatters, columnConfig, DataContext, props.theme, props.args.fitRowToContent), [formatters, columnConfig, props.theme, props.args.fitRowToContent]); const columnVisibility = (0, _react.useMemo)(() => ({ visibleColumns, setVisibleColumns: () => {} }), [visibleColumns]); const sorting = (0, _react.useMemo)(() => (0, _table_actions.createGridSortingConfig)(sortBy, sortDirection, onEditAction), [onEditAction, sortBy, sortDirection]); const renderSummaryRow = (0, _react.useMemo)(() => { const columnsWithSummary = columnConfig.columns.filter(col => !!col.columnId && !col.hidden).map(config => ({ columnId: config.columnId, summaryRowValue: config.summaryRowValue, ...(0, _summary.getFinalSummaryConfiguration)(config.columnId, config, props.data) })).filter(({ summaryRow }) => summaryRow !== 'none'); if (columnsWithSummary.length) { const summaryLookup = Object.fromEntries(columnsWithSummary.map(({ summaryRowValue, summaryLabel, columnId }) => [columnId, summaryLabel === '' ? `${summaryRowValue}` : `${summaryLabel}: ${summaryRowValue}`])); return ({ columnId }) => { var _columns$find, _columns$find$display; const currentAlignment = alignments && alignments[columnId]; const alignmentClassName = `lnsTableCell--${currentAlignment}`; const columnName = ((_columns$find = columns.find(({ id }) => id === columnId)) === null || _columns$find === void 0 ? void 0 : (_columns$find$display = _columns$find.displayAsText) === null || _columns$find$display === void 0 ? void 0 : _columns$find$display.replace(/ /g, '-')) || columnId; return summaryLookup[columnId] != null ? /*#__PURE__*/_react.default.createElement("div", { className: `lnsTableCell ${alignmentClassName}`, "data-test-subj": `lnsDataTable-footer-${columnName}` }, summaryLookup[columnId]) : null; }; } }, [columnConfig.columns, alignments, props.data, columns]); if (isEmpty) { return /*#__PURE__*/_react.default.createElement(_visualization_container.VisualizationContainer, { className: "lnsDataTableContainer" }, /*#__PURE__*/_react.default.createElement(_public.EmptyPlaceholder, { icon: _chartIcons.IconChartDatatable })); } const dataGridAriaLabel = props.args.title || _i18n.i18n.translate('xpack.lens.table.defaultAriaLabel', { defaultMessage: 'Data table visualization' }); return /*#__PURE__*/_react.default.createElement(_visualization_container.VisualizationContainer, { className: "lnsDataTableContainer" }, /*#__PURE__*/_react.default.createElement(DataContext.Provider, { value: { table: firstLocalTable, rowHasRowClickTriggerActions: props.rowHasRowClickTriggerActions, alignments, minMaxByColumnId, getColorForValue: props.paletteService.get(_coloring.CUSTOM_PALETTE).getColorForValue, handleFilterClick } }, /*#__PURE__*/_react.default.createElement(_eui.EuiDataGrid, { "aria-label": dataGridAriaLabel, "data-test-subj": "lnsDataTable", rowHeightsOptions: { defaultHeight: props.args.fitRowToContent ? 'auto' : props.args.rowHeightLines && props.args.rowHeightLines !== 1 ? { lineCount: props.args.rowHeightLines } : undefined }, columns: columns, columnVisibility: columnVisibility, trailingControlColumns: trailingControlColumns, rowCount: firstLocalTable.rows.length, renderCellValue: renderCellValue, gridStyle: gridStyle, sorting: sorting, pagination: pagination && { ...pagination, pageSizeOptions: PAGE_SIZE_OPTIONS, onChangeItemsPerPage, onChangePage }, onColumnResize: onColumnResize, toolbarVisibility: false, renderFooterCellValue: renderSummaryRow, ref: dataGridRef }))); }; exports.DatatableComponent = DatatableComponent;