Skip to content

Commit 85f19c3

Browse files
authored
feat(SchemaViewer): calculate column width based on data (#1885)
1 parent 54c7091 commit 85f19c3

File tree

8 files changed

+180
-73
lines changed

8 files changed

+180
-73
lines changed

src/components/QueryResultTable/QueryResultTable.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ import type {Column, Settings} from '@gravity-ui/react-data-table';
66
import type {ColumnType, KeyValueRow} from '../../types/api/query';
77
import {cn} from '../../utils/cn';
88
import {DEFAULT_TABLE_SETTINGS} from '../../utils/constants';
9+
import {getColumnWidth} from '../../utils/getColumnWidth';
910
import {getColumnType, prepareQueryResponse} from '../../utils/query';
1011
import {isNumeric} from '../../utils/utils';
1112
import type {ResizeableDataTableProps} from '../ResizeableDataTable/ResizeableDataTable';
1213
import {ResizeableDataTable} from '../ResizeableDataTable/ResizeableDataTable';
1314

1415
import {Cell} from './Cell';
1516
import i18n from './i18n';
16-
import {getColumnWidth} from './utils/getColumnWidth';
1717

1818
import './QueryResultTable.scss';
1919

src/components/QueryResultTable/utils/getColumnWidth.test.ts

Lines changed: 0 additions & 37 deletions
This file was deleted.

src/components/QueryResultTable/utils/getColumnWidth.ts

Lines changed: 0 additions & 21 deletions
This file was deleted.

src/containers/Tenant/Schema/SchemaViewer/SchemaViewer.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,20 +71,20 @@ export const SchemaViewer = ({type, path, tenantName, extended = false}: SchemaV
7171

7272
const columns = React.useMemo(() => {
7373
if (isViewType(type)) {
74-
return getViewColumns();
74+
return getViewColumns(tableData);
7575
}
7676
if (isExternalTableType(type)) {
77-
return getExternalTableColumns();
77+
return getExternalTableColumns(tableData);
7878
}
7979
if (isColumnEntityType(type)) {
80-
return getColumnTableColumns();
80+
return getColumnTableColumns(tableData);
8181
}
8282
if (isRowTableType(type)) {
83-
return getRowTableColumns(extended, hasAutoIncrement, hasDefaultValue);
83+
return getRowTableColumns(tableData, extended, hasAutoIncrement, hasDefaultValue);
8484
}
8585

8686
return [];
87-
}, [type, extended, hasAutoIncrement, hasDefaultValue]);
87+
}, [type, extended, hasAutoIncrement, hasDefaultValue, tableData]);
8888

8989
if (loading || isViewSchemaLoading) {
9090
return <TableSkeleton />;

src/containers/Tenant/Schema/SchemaViewer/columns.tsx

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import DataTable from '@gravity-ui/react-data-table';
22

3+
import {getColumnWidth} from '../../../../utils/getColumnWidth';
4+
35
import i18n from './i18n';
46
import type {SchemaColumn, SchemaData} from './types';
57

@@ -108,16 +110,37 @@ const compressionColumn: SchemaColumn = {
108110
render: ({row}) => row.columnCodec,
109111
};
110112

111-
export function getViewColumns(): SchemaColumn[] {
112-
return [nameColumn, typeColumn];
113+
const WIDTH_PREDICTION_ROWS_COUNT = 100;
114+
115+
function normalizeColumns(columns: SchemaColumn[], data?: SchemaData[]) {
116+
if (!data) {
117+
return columns;
118+
}
119+
const dataSlice = data.slice(0, WIDTH_PREDICTION_ROWS_COUNT);
120+
return columns.map((column) => {
121+
return {
122+
...column,
123+
width: getColumnWidth({
124+
data: dataSlice,
125+
name: column.name,
126+
header: typeof column.header === 'string' ? column.header : undefined,
127+
sortable: column.sortable || column.sortable === undefined,
128+
}),
129+
};
130+
});
131+
}
132+
133+
export function getViewColumns(data?: SchemaData[]): SchemaColumn[] {
134+
return normalizeColumns([nameColumn, typeColumn], data);
113135
}
114-
export function getExternalTableColumns(): SchemaColumn[] {
115-
return [idColumn, nameColumn, typeColumn, notNullColumn];
136+
export function getExternalTableColumns(data?: SchemaData[]): SchemaColumn[] {
137+
return normalizeColumns([idColumn, nameColumn, typeColumn, notNullColumn], data);
116138
}
117-
export function getColumnTableColumns(): SchemaColumn[] {
118-
return [idColumn, nameColumn, typeColumn, notNullColumn];
139+
export function getColumnTableColumns(data?: SchemaData[]): SchemaColumn[] {
140+
return normalizeColumns([idColumn, nameColumn, typeColumn, notNullColumn], data);
119141
}
120142
export function getRowTableColumns(
143+
data: SchemaData[] | undefined,
121144
extended: boolean,
122145
hasAutoIncrement: boolean,
123146
hasDefaultValue: boolean,
@@ -136,5 +159,5 @@ export function getRowTableColumns(
136159
rowTableColumns.push(autoIncrementColumn);
137160
}
138161

139-
return rowTableColumns;
162+
return normalizeColumns(rowTableColumns, data);
140163
}

src/containers/Tenant/Schema/SchemaViewer/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type {Column} from '@gravity-ui/react-data-table';
22

3-
export interface SchemaData {
3+
export type SchemaData = {
44
id?: number;
55
name?: string;
66
keyColumnIndex?: number;
@@ -12,7 +12,7 @@ export interface SchemaData {
1212
prefferedPoolKind?: string;
1313
columnCodec?: string;
1414
defaultValue?: string | number | boolean;
15-
}
15+
};
1616

1717
export interface SchemaColumn extends Column<SchemaData> {
1818
name: keyof SchemaData;
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import {
2+
HEADER_PADDING,
3+
MAX_COLUMN_WIDTH,
4+
PIXELS_PER_CHARACTER,
5+
SORT_ICON_TO_CHARACTERS,
6+
getColumnWidth,
7+
} from '../getColumnWidth';
8+
9+
describe('getColumnWidth', () => {
10+
it('returns minimum width for empty data', () => {
11+
const result = getColumnWidth({data: [], name: 'test'});
12+
expect(result).toBe(HEADER_PADDING + 'test'.length * PIXELS_PER_CHARACTER);
13+
});
14+
15+
it('calculates correct width for string columns', () => {
16+
const data = [{test: 'short'}, {test: 'medium length'}, {test: 'this is a longer string'}];
17+
const result = getColumnWidth({data, name: 'test'});
18+
expect(result).toBe(
19+
HEADER_PADDING + 'this is a longer string'.length * PIXELS_PER_CHARACTER,
20+
);
21+
});
22+
23+
it('calculates correct width for columns with sorting', () => {
24+
const result = getColumnWidth({data: [], name: 'test', sortable: true});
25+
expect(result).toBe(
26+
HEADER_PADDING + ('test'.length + SORT_ICON_TO_CHARACTERS) * PIXELS_PER_CHARACTER,
27+
);
28+
});
29+
it('calculates correct width for columns with sorting and column name wider than header', () => {
30+
const data = [{test: 'this is a longer string'}];
31+
const result = getColumnWidth({data, name: 'test', sortable: true});
32+
expect(result).toBe(
33+
HEADER_PADDING + 'this is a longer string'.length * PIXELS_PER_CHARACTER,
34+
);
35+
});
36+
37+
it('calculates correct width for columns with header', () => {
38+
const result = getColumnWidth({data: [], name: 'test', header: 'a'});
39+
expect(result).toBe(HEADER_PADDING + 'a'.length * PIXELS_PER_CHARACTER);
40+
});
41+
42+
it('returns MAX_COLUMN_WIDTH when calculated width exceeds it', () => {
43+
const data = [{test: 'a'.repeat(100)}];
44+
const result = getColumnWidth({data, name: 'test'});
45+
expect(result).toBe(MAX_COLUMN_WIDTH);
46+
});
47+
48+
it('handles undefined data correctly', () => {
49+
const result = getColumnWidth({name: 'test'});
50+
expect(result).toBe(HEADER_PADDING + 'test'.length * PIXELS_PER_CHARACTER);
51+
});
52+
53+
it('handles missing values in data correctly', () => {
54+
const data = [{test: 'short'}, {}, {test: 'longer string'}];
55+
const result = getColumnWidth({data, name: 'test'});
56+
expect(result).toBe(HEADER_PADDING + 'longer string'.length * PIXELS_PER_CHARACTER);
57+
});
58+
59+
it('uses column name length when all values are shorter', () => {
60+
const data = [{longColumnName: 'a'}, {longColumnName: 'bb'}];
61+
const result = getColumnWidth({data, name: 'longColumnName'});
62+
expect(result).toBe(HEADER_PADDING + 'longColumnName'.length * PIXELS_PER_CHARACTER);
63+
});
64+
65+
it('handles null values in data correctly', () => {
66+
const data = [{test: 'a'}, {test: null}];
67+
const result = getColumnWidth({data, name: 'test'});
68+
expect(result).toBe(HEADER_PADDING + 'test'.length * PIXELS_PER_CHARACTER);
69+
});
70+
71+
it('handles undefined values in data correctly', () => {
72+
const data = [{test: 'a'}, {test: undefined}];
73+
const result = getColumnWidth({data, name: 'test'});
74+
expect(result).toBe(HEADER_PADDING + 'test'.length * PIXELS_PER_CHARACTER);
75+
});
76+
77+
it('handles empty string values in data correctly', () => {
78+
const data = [{test: 'short'}, {test: ''}, {test: 'longer string'}];
79+
const result = getColumnWidth({data, name: 'test'});
80+
expect(result).toBe(HEADER_PADDING + 'longer string'.length * PIXELS_PER_CHARACTER);
81+
});
82+
83+
it('handles an array of numbers correctly', () => {
84+
const data = [{test: 1}, {test: 123}, {test: 12345}];
85+
const result = getColumnWidth({data, name: 'test'});
86+
expect(result).toBe(HEADER_PADDING + '12345'.length * PIXELS_PER_CHARACTER);
87+
});
88+
89+
it('handles an array of mixed data types correctly', () => {
90+
const data = [{test: 'short'}, {test: 123}, {test: null}, {test: 'longer string'}];
91+
const result = getColumnWidth({data, name: 'test'});
92+
expect(result).toBe(HEADER_PADDING + 'longer string'.length * PIXELS_PER_CHARACTER);
93+
});
94+
95+
it('handles empty name correctly', () => {
96+
const data = [{test: 'test'}];
97+
const result = getColumnWidth({data, name: ''});
98+
expect(result).toBe(HEADER_PADDING);
99+
});
100+
});

src/utils/getColumnWidth.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
export const MAX_COLUMN_WIDTH = 600;
2+
export const HEADER_PADDING = 20;
3+
export const SORT_ICON_TO_CHARACTERS = 2;
4+
export const PIXELS_PER_CHARACTER = 10;
5+
6+
export function getColumnWidth({
7+
data,
8+
name,
9+
header,
10+
sortable,
11+
}: {
12+
data?: Record<string, unknown>[];
13+
name: string;
14+
header?: string;
15+
sortable?: boolean;
16+
}) {
17+
const headerContentLength = typeof header === 'string' ? header.length : name.length;
18+
19+
let maxColumnContentLength = sortable
20+
? headerContentLength + SORT_ICON_TO_CHARACTERS
21+
: headerContentLength;
22+
23+
if (data) {
24+
for (const row of data) {
25+
let cellLength = 0;
26+
if (row[name]) {
27+
cellLength = String(row[name]).length;
28+
}
29+
30+
maxColumnContentLength = Math.max(maxColumnContentLength, cellLength);
31+
32+
if (
33+
maxColumnContentLength * PIXELS_PER_CHARACTER + HEADER_PADDING >=
34+
MAX_COLUMN_WIDTH
35+
) {
36+
return MAX_COLUMN_WIDTH;
37+
}
38+
}
39+
}
40+
41+
return maxColumnContentLength * PIXELS_PER_CHARACTER + HEADER_PADDING;
42+
}

0 commit comments

Comments
 (0)