diff --git a/src/components/QueryResultTable/QueryResultTable.tsx b/src/components/QueryResultTable/QueryResultTable.tsx index 3623e959ae..8319e46cb2 100644 --- a/src/components/QueryResultTable/QueryResultTable.tsx +++ b/src/components/QueryResultTable/QueryResultTable.tsx @@ -6,6 +6,7 @@ import type {Column, Settings} from '@gravity-ui/react-data-table'; import type {ColumnType, KeyValueRow} from '../../types/api/query'; import {cn} from '../../utils/cn'; import {DEFAULT_TABLE_SETTINGS} from '../../utils/constants'; +import {getColumnWidth} from '../../utils/getColumnWidth'; import {getColumnType, prepareQueryResponse} from '../../utils/query'; import {isNumeric} from '../../utils/utils'; import type {ResizeableDataTableProps} from '../ResizeableDataTable/ResizeableDataTable'; @@ -13,7 +14,6 @@ import {ResizeableDataTable} from '../ResizeableDataTable/ResizeableDataTable'; import {Cell} from './Cell'; import i18n from './i18n'; -import {getColumnWidth} from './utils/getColumnWidth'; import './QueryResultTable.scss'; diff --git a/src/components/QueryResultTable/utils/getColumnWidth.test.ts b/src/components/QueryResultTable/utils/getColumnWidth.test.ts deleted file mode 100644 index 71cc1e6b41..0000000000 --- a/src/components/QueryResultTable/utils/getColumnWidth.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import {HEADER_PADDING, MAX_COLUMN_WIDTH, getColumnWidth} from './getColumnWidth'; - -describe('getColumnWidth', () => { - 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 deleted file mode 100644 index 9a8d23971c..0000000000 --- a/src/components/QueryResultTable/utils/getColumnWidth.ts +++ /dev/null @@ -1,21 +0,0 @@ -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/Schema/SchemaViewer/SchemaViewer.tsx b/src/containers/Tenant/Schema/SchemaViewer/SchemaViewer.tsx index b12a1d83ad..5361e04bfb 100644 --- a/src/containers/Tenant/Schema/SchemaViewer/SchemaViewer.tsx +++ b/src/containers/Tenant/Schema/SchemaViewer/SchemaViewer.tsx @@ -71,20 +71,20 @@ export const SchemaViewer = ({type, path, tenantName, extended = false}: SchemaV const columns = React.useMemo(() => { if (isViewType(type)) { - return getViewColumns(); + return getViewColumns(tableData); } if (isExternalTableType(type)) { - return getExternalTableColumns(); + return getExternalTableColumns(tableData); } if (isColumnEntityType(type)) { - return getColumnTableColumns(); + return getColumnTableColumns(tableData); } if (isRowTableType(type)) { - return getRowTableColumns(extended, hasAutoIncrement, hasDefaultValue); + return getRowTableColumns(tableData, extended, hasAutoIncrement, hasDefaultValue); } return []; - }, [type, extended, hasAutoIncrement, hasDefaultValue]); + }, [type, extended, hasAutoIncrement, hasDefaultValue, tableData]); if (loading || isViewSchemaLoading) { return ; diff --git a/src/containers/Tenant/Schema/SchemaViewer/columns.tsx b/src/containers/Tenant/Schema/SchemaViewer/columns.tsx index 210a8a6a98..ec87df93e3 100644 --- a/src/containers/Tenant/Schema/SchemaViewer/columns.tsx +++ b/src/containers/Tenant/Schema/SchemaViewer/columns.tsx @@ -1,5 +1,7 @@ import DataTable from '@gravity-ui/react-data-table'; +import {getColumnWidth} from '../../../../utils/getColumnWidth'; + import i18n from './i18n'; import type {SchemaColumn, SchemaData} from './types'; @@ -108,16 +110,37 @@ const compressionColumn: SchemaColumn = { render: ({row}) => row.columnCodec, }; -export function getViewColumns(): SchemaColumn[] { - return [nameColumn, typeColumn]; +const WIDTH_PREDICTION_ROWS_COUNT = 100; + +function normalizeColumns(columns: SchemaColumn[], data?: SchemaData[]) { + if (!data) { + return columns; + } + const dataSlice = data.slice(0, WIDTH_PREDICTION_ROWS_COUNT); + return columns.map((column) => { + return { + ...column, + width: getColumnWidth({ + data: dataSlice, + name: column.name, + header: typeof column.header === 'string' ? column.header : undefined, + sortable: column.sortable || column.sortable === undefined, + }), + }; + }); +} + +export function getViewColumns(data?: SchemaData[]): SchemaColumn[] { + return normalizeColumns([nameColumn, typeColumn], data); } -export function getExternalTableColumns(): SchemaColumn[] { - return [idColumn, nameColumn, typeColumn, notNullColumn]; +export function getExternalTableColumns(data?: SchemaData[]): SchemaColumn[] { + return normalizeColumns([idColumn, nameColumn, typeColumn, notNullColumn], data); } -export function getColumnTableColumns(): SchemaColumn[] { - return [idColumn, nameColumn, typeColumn, notNullColumn]; +export function getColumnTableColumns(data?: SchemaData[]): SchemaColumn[] { + return normalizeColumns([idColumn, nameColumn, typeColumn, notNullColumn], data); } export function getRowTableColumns( + data: SchemaData[] | undefined, extended: boolean, hasAutoIncrement: boolean, hasDefaultValue: boolean, @@ -136,5 +159,5 @@ export function getRowTableColumns( rowTableColumns.push(autoIncrementColumn); } - return rowTableColumns; + return normalizeColumns(rowTableColumns, data); } diff --git a/src/containers/Tenant/Schema/SchemaViewer/types.ts b/src/containers/Tenant/Schema/SchemaViewer/types.ts index 3ed84ffff9..9a77d19591 100644 --- a/src/containers/Tenant/Schema/SchemaViewer/types.ts +++ b/src/containers/Tenant/Schema/SchemaViewer/types.ts @@ -1,6 +1,6 @@ import type {Column} from '@gravity-ui/react-data-table'; -export interface SchemaData { +export type SchemaData = { id?: number; name?: string; keyColumnIndex?: number; @@ -12,7 +12,7 @@ export interface SchemaData { prefferedPoolKind?: string; columnCodec?: string; defaultValue?: string | number | boolean; -} +}; export interface SchemaColumn extends Column { name: keyof SchemaData; diff --git a/src/utils/__test__/getColumnWidth.test.ts b/src/utils/__test__/getColumnWidth.test.ts new file mode 100644 index 0000000000..26bf8387fb --- /dev/null +++ b/src/utils/__test__/getColumnWidth.test.ts @@ -0,0 +1,100 @@ +import { + HEADER_PADDING, + MAX_COLUMN_WIDTH, + PIXELS_PER_CHARACTER, + SORT_ICON_TO_CHARACTERS, + getColumnWidth, +} from '../getColumnWidth'; + +describe('getColumnWidth', () => { + it('returns minimum width for empty data', () => { + const result = getColumnWidth({data: [], name: 'test'}); + expect(result).toBe(HEADER_PADDING + 'test'.length * PIXELS_PER_CHARACTER); + }); + + 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 * PIXELS_PER_CHARACTER, + ); + }); + + it('calculates correct width for columns with sorting', () => { + const result = getColumnWidth({data: [], name: 'test', sortable: true}); + expect(result).toBe( + HEADER_PADDING + ('test'.length + SORT_ICON_TO_CHARACTERS) * PIXELS_PER_CHARACTER, + ); + }); + it('calculates correct width for columns with sorting and column name wider than header', () => { + const data = [{test: 'this is a longer string'}]; + const result = getColumnWidth({data, name: 'test', sortable: true}); + expect(result).toBe( + HEADER_PADDING + 'this is a longer string'.length * PIXELS_PER_CHARACTER, + ); + }); + + it('calculates correct width for columns with header', () => { + const result = getColumnWidth({data: [], name: 'test', header: 'a'}); + expect(result).toBe(HEADER_PADDING + 'a'.length * PIXELS_PER_CHARACTER); + }); + + 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 * PIXELS_PER_CHARACTER); + }); + + 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 * PIXELS_PER_CHARACTER); + }); + + 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 * PIXELS_PER_CHARACTER); + }); + + it('handles null values in data correctly', () => { + const data = [{test: 'a'}, {test: null}]; + const result = getColumnWidth({data, name: 'test'}); + expect(result).toBe(HEADER_PADDING + 'test'.length * PIXELS_PER_CHARACTER); + }); + + it('handles undefined values in data correctly', () => { + const data = [{test: 'a'}, {test: undefined}]; + const result = getColumnWidth({data, name: 'test'}); + expect(result).toBe(HEADER_PADDING + 'test'.length * PIXELS_PER_CHARACTER); + }); + + it('handles empty string values in data correctly', () => { + const data = [{test: 'short'}, {test: ''}, {test: 'longer string'}]; + const result = getColumnWidth({data, name: 'test'}); + expect(result).toBe(HEADER_PADDING + 'longer string'.length * PIXELS_PER_CHARACTER); + }); + + it('handles an array of numbers correctly', () => { + const data = [{test: 1}, {test: 123}, {test: 12345}]; + const result = getColumnWidth({data, name: 'test'}); + expect(result).toBe(HEADER_PADDING + '12345'.length * PIXELS_PER_CHARACTER); + }); + + it('handles an array of mixed data types correctly', () => { + const data = [{test: 'short'}, {test: 123}, {test: null}, {test: 'longer string'}]; + const result = getColumnWidth({data, name: 'test'}); + expect(result).toBe(HEADER_PADDING + 'longer string'.length * PIXELS_PER_CHARACTER); + }); + + it('handles empty name correctly', () => { + const data = [{test: 'test'}]; + const result = getColumnWidth({data, name: ''}); + expect(result).toBe(HEADER_PADDING); + }); +}); diff --git a/src/utils/getColumnWidth.ts b/src/utils/getColumnWidth.ts new file mode 100644 index 0000000000..ae2dea5e5c --- /dev/null +++ b/src/utils/getColumnWidth.ts @@ -0,0 +1,42 @@ +export const MAX_COLUMN_WIDTH = 600; +export const HEADER_PADDING = 20; +export const SORT_ICON_TO_CHARACTERS = 2; +export const PIXELS_PER_CHARACTER = 10; + +export function getColumnWidth({ + data, + name, + header, + sortable, +}: { + data?: Record[]; + name: string; + header?: string; + sortable?: boolean; +}) { + const headerContentLength = typeof header === 'string' ? header.length : name.length; + + let maxColumnContentLength = sortable + ? headerContentLength + SORT_ICON_TO_CHARACTERS + : headerContentLength; + + if (data) { + for (const row of data) { + let cellLength = 0; + if (row[name]) { + cellLength = String(row[name]).length; + } + + maxColumnContentLength = Math.max(maxColumnContentLength, cellLength); + + if ( + maxColumnContentLength * PIXELS_PER_CHARACTER + HEADER_PADDING >= + MAX_COLUMN_WIDTH + ) { + return MAX_COLUMN_WIDTH; + } + } + } + + return maxColumnContentLength * PIXELS_PER_CHARACTER + HEADER_PADDING; +}