/* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ import './expression.scss'; import React, { useMemo } from 'react'; import ReactDOM from 'react-dom'; import { i18n } from '@kbn/i18n'; import { I18nProvider } from '@kbn/i18n/react'; import { EuiBasicTable, EuiFlexGroup, EuiButtonIcon, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import { IAggType } from 'src/plugins/data/public'; import { FormatFactory, ILensInterpreterRenderHandlers, LensFilterEvent, LensMultiTable, } from '../types'; import { ExpressionFunctionDefinition, ExpressionRenderDefinition, } from '../../../../../src/plugins/expressions/public'; import { VisualizationContainer } from '../visualization_container'; import { EmptyPlaceholder } from '../shared_components'; import { desanitizeFilterContext } from '../utils'; import { LensIconChartDatatable } from '../assets/chart_datatable'; export interface DatatableColumns { columnIds: string[]; } interface Args { title: string; columns: DatatableColumns & { type: 'lens_datatable_columns' }; } export interface DatatableProps { data: LensMultiTable; args: Args; } type DatatableRenderProps = DatatableProps & { formatFactory: FormatFactory; onClickValue: (data: LensFilterEvent['data']) => void; getType: (name: string) => IAggType; }; export interface DatatableRender { type: 'render'; as: 'lens_datatable_renderer'; value: DatatableProps; } export const datatable: ExpressionFunctionDefinition< 'lens_datatable', LensMultiTable, Args, DatatableRender > = { name: 'lens_datatable', type: 'render', inputTypes: ['lens_multitable'], help: i18n.translate('xpack.lens.datatable.expressionHelpLabel', { defaultMessage: 'Datatable renderer', }), args: { title: { types: ['string'], help: i18n.translate('xpack.lens.datatable.titleLabel', { defaultMessage: 'Title', }), }, columns: { types: ['lens_datatable_columns'], help: '', }, }, fn(data, args) { return { type: 'render', as: 'lens_datatable_renderer', value: { data, args, }, }; }, }; type DatatableColumnsResult = DatatableColumns & { type: 'lens_datatable_columns' }; export const datatableColumns: ExpressionFunctionDefinition< 'lens_datatable_columns', null, DatatableColumns, DatatableColumnsResult > = { name: 'lens_datatable_columns', aliases: [], type: 'lens_datatable_columns', help: '', inputTypes: ['null'], args: { columnIds: { types: ['string'], multi: true, help: '', }, }, fn: function fn(input: unknown, args: DatatableColumns) { return { type: 'lens_datatable_columns', ...args, }; }, }; export const getDatatableRenderer = (dependencies: { formatFactory: Promise; getType: Promise<(name: string) => IAggType>; }): ExpressionRenderDefinition => ({ name: 'lens_datatable_renderer', displayName: i18n.translate('xpack.lens.datatable.visualizationName', { defaultMessage: 'Datatable', }), help: '', validate: () => undefined, reuseDomNode: true, render: async ( domNode: Element, config: DatatableProps, handlers: ILensInterpreterRenderHandlers ) => { const resolvedFormatFactory = await dependencies.formatFactory; const resolvedGetType = await dependencies.getType; const onClickValue = (data: LensFilterEvent['data']) => { handlers.event({ name: 'filter', data }); }; ReactDOM.render( , domNode, () => { handlers.done(); } ); handlers.onDestroy(() => ReactDOM.unmountComponentAtNode(domNode)); }, }); export function DatatableComponent(props: DatatableRenderProps) { const [firstTable] = Object.values(props.data.tables); const formatters: Record> = {}; firstTable.columns.forEach((column) => { formatters[column.id] = props.formatFactory(column.formatHint); }); const { onClickValue } = props; const handleFilterClick = useMemo( () => (field: string, value: unknown, colIndex: number, negate: boolean = false) => { const col = firstTable.columns[colIndex]; const isDate = col.meta?.type === 'date_histogram' || col.meta?.type === 'date_range'; const timeFieldName = negate && isDate ? undefined : col?.meta?.aggConfigParams?.field; const rowIndex = firstTable.rows.findIndex((row) => row[field] === value); const data: LensFilterEvent['data'] = { negate, data: [ { row: rowIndex, column: colIndex, value, table: firstTable, }, ], timeFieldName, }; onClickValue(desanitizeFilterContext(data)); }, [firstTable, onClickValue] ); const bucketColumns = firstTable.columns .filter((col) => { return col?.meta?.type && props.getType(col.meta.type)?.type === 'buckets'; }) .map((col) => col.id); const isEmpty = firstTable.rows.length === 0 || (bucketColumns.length && firstTable.rows.every((row) => bucketColumns.every((col) => typeof row[col] === 'undefined') )); if (isEmpty) { return ; } return ( { const col = firstTable.columns.find((c) => c.id === field); const filterable = bucketColumns.includes(field); const colIndex = firstTable.columns.findIndex((c) => c.id === field); return { field, name: (col && col.name) || '', render: (value: unknown) => { const formattedValue = formatters[field]?.convert(value); const fieldName = col?.meta?.aggConfigParams?.field; if (filterable) { return ( {formattedValue} handleFilterClick(field, value, colIndex)} /> handleFilterClick(field, value, colIndex, true)} /> ); } return {formattedValue}; }, }; }) .filter(({ field }) => !!field)} items={firstTable ? firstTable.rows : []} /> ); }