diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/__snapshots__/table_basic.test.tsx.snap b/x-pack/plugins/lens/public/datatable_visualization/components/__snapshots__/table_basic.test.tsx.snap index 158c7fa4aeec37..992301af13ad04 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/__snapshots__/table_basic.test.tsx.snap +++ b/x-pack/plugins/lens/public/datatable_visualization/components/__snapshots__/table_basic.test.tsx.snap @@ -8,6 +8,11 @@ exports[`DatatableComponent it renders actions column when there are row actions { + const table: Datatable = { + type: 'datatable', + columns: [ + { + id: 'a', + name: 'a', + meta: { + type: 'number', + }, + }, + ], + rows: [{ a: 123 }], + }; + const CellRenderer = createGridCell( + { + a: { convert: (x) => `formatted ${x}` } as FieldFormat, + }, + DataContext + ); + + it('renders formatted value', () => { + const instance = mountWithIntl( + + {}} + isExpandable={false} + isDetails={false} + isExpanded={false} + /> + + ); + expect(instance.text()).toEqual('formatted 123'); + }); + + it('set class with text alignment', () => { + const cell = mountWithIntl( + + {}} + isExpandable={false} + isDetails={false} + isExpanded={false} + /> + + ); + expect(cell.find('.lnsTableCell').prop('className')).toContain('--right'); + }); +}); diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.tsx index a6e1e3386bcf35..2261dd06b532ba 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.tsx @@ -14,19 +14,21 @@ export const createGridCell = ( formatters: Record>, DataContext: React.Context ) => ({ rowIndex, columnId }: EuiDataGridCellValueElementProps) => { - const { table } = useContext(DataContext); + const { table, alignments } = useContext(DataContext); const rowValue = table?.rows[rowIndex][columnId]; const content = formatters[columnId]?.convert(rowValue, 'html'); + const currentAlignment = alignments && alignments[columnId]; + const alignmentClassName = `lnsTableCell--${currentAlignment}`; return ( - ); }; diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.test.tsx new file mode 100644 index 00000000000000..e0d31a3ed02012 --- /dev/null +++ b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.test.tsx @@ -0,0 +1,112 @@ +/* + * 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. + */ + +import React from 'react'; +import { EuiButtonGroup } from '@elastic/eui'; +import { FramePublicAPI, VisualizationDimensionEditorProps } from '../../types'; +import { DatatableVisualizationState } from '../visualization'; +import { createMockDatasource, createMockFramePublicAPI } from '../../editor_frame_service/mocks'; +import { mountWithIntl } from '@kbn/test/jest'; +import { TableDimensionEditor } from './dimension_editor'; + +describe('data table dimension editor', () => { + let frame: FramePublicAPI; + let state: DatatableVisualizationState; + let setState: (newState: DatatableVisualizationState) => void; + let props: VisualizationDimensionEditorProps; + + function testState(): DatatableVisualizationState { + return { + layerId: 'first', + columns: [ + { + columnId: 'foo', + }, + ], + }; + } + + beforeEach(() => { + state = testState(); + frame = createMockFramePublicAPI(); + frame.datasourceLayers = { + first: createMockDatasource('test').publicAPIMock, + }; + frame.activeData = { + first: { + type: 'datatable', + columns: [ + { + id: 'foo', + name: 'foo', + meta: { + type: 'string', + }, + }, + ], + rows: [], + }, + }; + setState = jest.fn(); + props = { + accessor: 'foo', + frame, + groupId: 'columns', + layerId: 'first', + state, + setState, + }; + }); + + it('should render default alignment', () => { + const instance = mountWithIntl(); + expect(instance.find(EuiButtonGroup).prop('idSelected')).toEqual( + expect.stringContaining('left') + ); + }); + + it('should render default alignment for number', () => { + frame.activeData!.first.columns[0].meta.type = 'number'; + const instance = mountWithIntl(); + expect(instance.find(EuiButtonGroup).prop('idSelected')).toEqual( + expect.stringContaining('right') + ); + }); + + it('should render specific alignment', () => { + state.columns[0].alignment = 'center'; + const instance = mountWithIntl(); + expect(instance.find(EuiButtonGroup).prop('idSelected')).toEqual( + expect.stringContaining('center') + ); + }); + + it('should set state for the right column', () => { + state.columns = [ + { + columnId: 'foo', + }, + { + columnId: 'bar', + }, + ]; + const instance = mountWithIntl(); + instance.find(EuiButtonGroup).prop('onChange')('center'); + expect(setState).toHaveBeenCalledWith({ + ...state, + columns: [ + { + columnId: 'foo', + alignment: 'center', + }, + { + columnId: 'bar', + }, + ], + }); + }); +}); diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx index 008b805bc8fed3..9c60cd47af3e34 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx @@ -7,55 +7,121 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiSwitch, EuiFormRow } from '@elastic/eui'; +import { EuiFormRow, EuiSwitch, EuiButtonGroup, htmlIdGenerator } from '@elastic/eui'; import { VisualizationDimensionEditorProps } from '../../types'; import { DatatableVisualizationState } from '../visualization'; +const idPrefix = htmlIdGenerator()(); + export function TableDimensionEditor( props: VisualizationDimensionEditorProps ) { - const { state, setState, accessor } = props; - const column = state.columns.find((c) => c.columnId === accessor); + const { state, setState, frame, accessor } = props; + const column = state.columns.find(({ columnId }) => accessor === columnId); - const visibleColumnsCount = state.columns.filter((c) => !c.hidden).length; + if (!column) return null; - if (!column) { - return null; - } + // either read config state or use same logic as chart itself + const currentAlignment = + column?.alignment || + (frame.activeData && + frame.activeData[state.layerId].columns.find((col) => col.id === accessor)?.meta.type === + 'number' + ? 'right' + : 'left'); + + const visibleColumnsCount = state.columns.filter((c) => !c.hidden).length; return ( - + + ); } diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.scss b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.scss index 5e5db2c6458095..b99ffb6dce8101 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.scss +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.scss @@ -1,3 +1,19 @@ .lnsDataTableContainer { height: 100%; } + +.lnsTableCell { + @include euiTextTruncate; +} + +.lnsTableCell--left { + text-align: left; +} + +.lnsTableCell--right { + text-align: right; +} + +.lnsTableCell--center { + text-align: center; +} \ No newline at end of file diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx index 588340fbe97fa9..22577e8ef5fd31 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx @@ -12,7 +12,7 @@ import { EuiDataGrid } from '@elastic/eui'; import { IAggType, IFieldFormat } from 'src/plugins/data/public'; import { EmptyPlaceholder } from '../../shared_components'; import { LensIconChartDatatable } from '../../assets/chart_datatable'; -import { DatatableComponent } from './table_basic'; +import { DataContext, DatatableComponent } from './table_basic'; import { LensMultiTable } from '../../types'; import { DatatableProps } from '../expression'; @@ -427,6 +427,39 @@ describe('DatatableComponent', () => { expect(wrapper.find(EuiDataGrid).prop('columns')!.length).toEqual(2); }); + test('it adds alignment data to context', () => { + const { data, args } = sampleArgs(); + + const wrapper = shallow( + ({ convert: (x) => x } as IFieldFormat)} + dispatchEvent={onDispatchEvent} + getType={jest.fn()} + renderMode="display" + /> + ); + + expect(wrapper.find(DataContext.Provider).prop('value').alignments).toEqual({ + // set via args + a: 'center', + // default for date + b: 'left', + // default for number + c: 'right', + }); + }); + test('it should refresh the table header when the datatable data changes', () => { const { data, args } = sampleArgs(); diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx index f685990f12dd26..e1687ba28f07bb 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx @@ -40,7 +40,7 @@ import { createGridSortingConfig, } from './table_actions'; -const DataContext = React.createContext({}); +export const DataContext = React.createContext({}); const gridStyle: EuiDataGridStyle = { border: 'horizontal', @@ -192,6 +192,21 @@ export const DatatableComponent = (props: DatatableRenderProps) => { ] ); + const alignments: Record = useMemo(() => { + const alignmentMap: Record = {}; + columnConfig.columns.forEach((column) => { + if (column.alignment) { + alignmentMap[column.columnId] = column.alignment; + } else { + const isNumeric = + firstLocalTable.columns.find((dataColumn) => dataColumn.id === column.columnId)?.meta + .type === 'number'; + alignmentMap[column.columnId] = isNumeric ? 'right' : 'left'; + } + }); + return alignmentMap; + }, [firstLocalTable, columnConfig]); + const trailingControlColumns: EuiDataGridControlColumn[] = useMemo(() => { if (!hasAtLeastOneRowClickAction || !onRowContextMenuClick) { return []; @@ -259,6 +274,7 @@ export const DatatableComponent = (props: DatatableRenderProps) => { value={{ table: firstLocalTable, rowHasRowClickTriggerActions: props.rowHasRowClickTriggerActions, + alignments, }} > ; } diff --git a/x-pack/plugins/lens/public/datatable_visualization/expression.tsx b/x-pack/plugins/lens/public/datatable_visualization/expression.tsx index 7ead7be67947c9..f6a38541cda271 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/expression.tsx @@ -138,6 +138,7 @@ export const datatableColumn: ExpressionFunctionDefinition< inputTypes: ['null'], args: { columnId: { types: ['string'], help: '' }, + alignment: { types: ['string'], help: '' }, hidden: { types: ['boolean'], help: '' }, width: { types: ['number'], help: '' }, }, diff --git a/x-pack/plugins/lens/public/datatable_visualization/visualization.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/visualization.test.tsx index ad5e1e552ccd2b..92136c557ad38d 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/visualization.test.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/visualization.test.tsx @@ -419,11 +419,13 @@ describe('Datatable Visualization', () => { columnId: ['c'], hidden: [], width: [], + alignment: [], }); expect(columnArgs[1].arguments).toEqual({ columnId: ['b'], hidden: [], width: [], + alignment: [], }); }); diff --git a/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx b/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx index 47f8ce09aea68c..fc69c914deb680 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx @@ -23,6 +23,7 @@ export interface ColumnState { columnId: string; width?: number; hidden?: boolean; + alignment?: 'left' | 'right' | 'center'; } export interface SortingState { @@ -264,6 +265,7 @@ export const datatableVisualization: Visualization columnId: [column.columnId], hidden: typeof column.hidden === 'undefined' ? [] : [column.hidden], width: typeof column.width === 'undefined' ? [] : [column.width], + alignment: typeof column.alignment === 'undefined' ? [] : [column.alignment], }, }, ],