diff --git a/src/components/QueryResultTable/QueryResultTable.tsx b/src/components/QueryResultTable/QueryResultTable.tsx index e92acc98d4..155b0e1415 100644 --- a/src/components/QueryResultTable/QueryResultTable.tsx +++ b/src/components/QueryResultTable/QueryResultTable.tsx @@ -13,6 +13,7 @@ import {ResizeableDataTable} from '../ResizeableDataTable/ResizeableDataTable'; import {Cell} from './Cell'; import i18n from './i18n'; +import {getColumnWidth} from './utils/getColumnWidth'; import './QueryResultTable.scss'; @@ -21,30 +22,27 @@ const TABLE_SETTINGS: Settings = { stripedRows: true, dynamicRenderType: 'variable', dynamicItemSizeGetter: () => 40, + sortable: false, }; export const b = cn('ydb-query-result-table'); -const prepareTypedColumns = (columns: ColumnType[]) => { +const WIDTH_PREDICTION_ROWS_COUNT = 100; + +const prepareTypedColumns = (columns: ColumnType[], data?: KeyValueRow[]) => { if (!columns.length) { return []; } + const dataSlice = data?.slice(0, WIDTH_PREDICTION_ROWS_COUNT); + return columns.map(({name, type}) => { const columnType = getColumnType(type); const column: Column = { name, + width: getColumnWidth({data: dataSlice, name}), align: columnType === 'number' ? DataTable.RIGHT : DataTable.LEFT, - sortAccessor: (row) => { - const value = row[name]; - - if (value === undefined || value === null) { - return null; - } - - return columnType === 'number' ? BigInt(value) : value; - }, render: ({row}) => , }; @@ -57,11 +55,13 @@ const prepareGenericColumns = (data: KeyValueRow[]) => { return []; } + const dataSlice = data?.slice(0, WIDTH_PREDICTION_ROWS_COUNT); + return Object.keys(data[0]).map((name) => { const column: Column = { name, + width: getColumnWidth({data: dataSlice, name}), align: isNumeric(data[0][name]) ? DataTable.RIGHT : DataTable.LEFT, - sortAccessor: (row) => (isNumeric(row[name]) ? Number(row[name]) : row[name]), render: ({row}) => , }; @@ -78,19 +78,12 @@ interface QueryResultTableProps } export const QueryResultTable = (props: QueryResultTableProps) => { - const {columns: rawColumns, data: rawData, settings: settingsMix, ...restProps} = props; + const {columns: rawColumns, data: rawData, ...restProps} = props; const data = React.useMemo(() => prepareQueryResponse(rawData), [rawData]); const columns = React.useMemo(() => { - return rawColumns ? prepareTypedColumns(rawColumns) : prepareGenericColumns(data); + return rawColumns ? prepareTypedColumns(rawColumns, data) : prepareGenericColumns(data); }, [data, rawColumns]); - const settings = React.useMemo( - () => ({ - ...TABLE_SETTINGS, - ...settingsMix, - }), - [settingsMix], - ); // empty data is expected to be be an empty array // undefined data is not rendered at all @@ -106,7 +99,7 @@ export const QueryResultTable = (props: QueryResultTableProps) => { { + it('returns minimum width for empty data', () => { + const result = getColumnWidth({data: [], name: 'test'}); + expect(result).toBe(HEADER_PADDING + 'test'.length * 10); + }); + + it('calculates correct width for string columns', () => { + const data = [{test: 'short'}, {test: 'medium length'}, {test: 'this is a longer string'}]; + const result = getColumnWidth({data, name: 'test'}); + expect(result).toBe(HEADER_PADDING + 'this is a longer string'.length * 10); + }); + + it('returns MAX_COLUMN_WIDTH when calculated width exceeds it', () => { + const data = [{test: 'a'.repeat(100)}]; + const result = getColumnWidth({data, name: 'test'}); + expect(result).toBe(MAX_COLUMN_WIDTH); + }); + + it('handles undefined data correctly', () => { + const result = getColumnWidth({name: 'test'}); + expect(result).toBe(HEADER_PADDING + 'test'.length * 10); + }); + + it('handles missing values in data correctly', () => { + const data = [{test: 'short'}, {}, {test: 'longer string'}]; + const result = getColumnWidth({data, name: 'test'}); + expect(result).toBe(HEADER_PADDING + 'longer string'.length * 10); + }); + + it('uses column name length when all values are shorter', () => { + const data = [{longColumnName: 'a'}, {longColumnName: 'bb'}]; + const result = getColumnWidth({data, name: 'longColumnName'}); + expect(result).toBe(HEADER_PADDING + 'longColumnName'.length * 10); + }); +}); diff --git a/src/components/QueryResultTable/utils/getColumnWidth.ts b/src/components/QueryResultTable/utils/getColumnWidth.ts new file mode 100644 index 0000000000..9a8d23971c --- /dev/null +++ b/src/components/QueryResultTable/utils/getColumnWidth.ts @@ -0,0 +1,21 @@ +import type {KeyValueRow} from '../../../types/api/query'; + +export const MAX_COLUMN_WIDTH = 600; +export const HEADER_PADDING = 20; + +export const getColumnWidth = ({data, name}: {data?: KeyValueRow[]; name: string}) => { + let maxColumnContentLength = name.length; + + if (data) { + for (const row of data) { + const cellLength = row[name] ? String(row[name]).length : 0; + maxColumnContentLength = Math.max(maxColumnContentLength, cellLength); + + if (maxColumnContentLength * 10 + HEADER_PADDING >= MAX_COLUMN_WIDTH) { + return MAX_COLUMN_WIDTH; + } + } + } + + return maxColumnContentLength * 10 + HEADER_PADDING; +}; diff --git a/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.tsx b/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.tsx index bc54e08350..7601490788 100644 --- a/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.tsx +++ b/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.tsx @@ -136,7 +136,6 @@ export function ExecuteResult({