Skip to content

Commit 4c25054

Browse files
authored
feat(TopicData): add tab for topic data (#2145)
1 parent 12214a8 commit 4c25054

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1717
-159
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type {TextInputProps} from '@gravity-ui/uikit';
2+
import {TextInput} from '@gravity-ui/uikit';
3+
4+
import {useDebouncedValue} from '../../utils/hooks/useDebouncedValue';
5+
6+
interface DebouncedInputProps extends TextInputProps {
7+
debounce?: number;
8+
}
9+
10+
export const DebouncedInput = ({
11+
onUpdate,
12+
value = '',
13+
debounce = 200,
14+
...rest
15+
}: DebouncedInputProps) => {
16+
const [currentValue, handleUpdate] = useDebouncedValue<string>({value, onUpdate, debounce});
17+
18+
return <TextInput value={currentValue} onUpdate={handleUpdate} {...rest} />;
19+
};

src/components/EmptyState/EmptyState.scss

+3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828

2929
margin: 0 auto;
3030
}
31+
&_position_left {
32+
margin: unset;
33+
}
3134
}
3235

3336
&__image {

src/components/MetricChart/getDefaultDataFormatter.ts

+4-13
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {formatBytes} from '../../utils/bytesParsers';
22
import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants';
33
import {roundToPrecision} from '../../utils/dataFormatters/dataFormatters';
44
import {formatToMs} from '../../utils/timeParsers';
5-
import {isNumeric} from '../../utils/utils';
5+
import {safeParseNumber} from '../../utils/utils';
66

77
import type {ChartDataType, ChartValue} from './types';
88

@@ -28,27 +28,18 @@ function formatChartValueToMs(value: ChartValue) {
2828
if (value === null) {
2929
return EMPTY_DATA_PLACEHOLDER;
3030
}
31-
return formatToMs(roundToPrecision(convertToNumber(value), 2));
31+
return formatToMs(roundToPrecision(safeParseNumber(value), 2));
3232
}
3333

3434
function formatChartValueToSize(value: ChartValue) {
3535
if (value === null) {
3636
return EMPTY_DATA_PLACEHOLDER;
3737
}
38-
return formatBytes({value: convertToNumber(value), precision: 3});
38+
return formatBytes({value: safeParseNumber(value), precision: 3});
3939
}
4040
function formatChartValueToPercent(value: ChartValue) {
4141
if (value === null) {
4242
return EMPTY_DATA_PLACEHOLDER;
4343
}
44-
return Math.round(convertToNumber(value) * 100) + '%';
45-
}
46-
47-
// Numeric values expected, not numeric value should be displayd as 0
48-
function convertToNumber(value: unknown): number {
49-
if (isNumeric(value)) {
50-
return Number(value);
51-
}
52-
53-
return 0;
44+
return Math.round(safeParseNumber(value) * 100) + '%';
5445
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.ydb-multiline-table-header {
2+
white-space: normal;
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import {cn} from '../../utils/cn';
2+
3+
import './MultilineTableHeader.scss';
4+
5+
const b = cn('ydb-multiline-table-header');
6+
7+
interface MultilineTableHeaderProps {
8+
title?: string;
9+
}
10+
11+
export function MultilineTableHeader({title}: MultilineTableHeaderProps) {
12+
if (!title) {
13+
return null;
14+
}
15+
return <div className={b()}>{title}</div>;
16+
}

src/components/PaginatedTable/PaginatedTable.scss

+32-13
Original file line numberDiff line numberDiff line change
@@ -79,50 +79,69 @@
7979
display: flex;
8080
flex-direction: row;
8181
align-items: center;
82+
gap: var(--g-spacing-2);
8283

8384
width: 100%;
8485
max-width: 100%;
8586
padding: var(--paginated-table-cell-vertical-padding)
8687
var(--paginated-table-cell-horizontal-padding);
8788

89+
font-weight: bold;
90+
cursor: default;
8891
&_align {
8992
&_left {
9093
justify-content: left;
94+
95+
text-align: left;
9196
}
9297
&_center {
9398
justify-content: center;
99+
100+
text-align: center;
94101
}
95102
&_right {
96103
justify-content: right;
104+
105+
text-align: right;
106+
107+
#{$block}__head-cell-content-container {
108+
flex-direction: row-reverse;
109+
}
97110
}
98111
}
99112
}
100113

101-
&__head-cell {
102-
gap: 8px;
103-
104-
font-weight: bold;
105-
cursor: default;
106-
107-
&_sortable {
108-
cursor: pointer;
114+
&__head-cell_sortable {
115+
cursor: pointer;
109116

110-
&#{$block}__head-cell_align_right {
111-
flex-direction: row-reverse;
112-
}
117+
&#{$block}__head-cell_align_right {
118+
flex-direction: row-reverse;
113119
}
114120
}
115121

122+
&__head-cell-note {
123+
display: flex;
124+
}
125+
116126
// Separate head cell content class for correct text ellipsis overflow
117127
&__head-cell-content {
118128
overflow: hidden;
119129

120-
width: min-content;
121-
122130
white-space: nowrap;
123131
text-overflow: ellipsis;
124132
}
125133

134+
&__head-cell-content-container {
135+
display: inline-flex;
136+
overflow: hidden;
137+
gap: var(--g-spacing-1);
138+
139+
.g-help-mark__button {
140+
display: inline-flex;
141+
align-items: center;
142+
}
143+
}
144+
126145
&__row-cell {
127146
display: table-cell;
128147
overflow-x: hidden;

src/components/PaginatedTable/PaginatedTable.tsx

+12-2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export interface PaginatedTableProps<T, F> {
3838
renderErrorMessage?: RenderErrorMessage;
3939
containerClassName?: string;
4040
onDataFetched?: (data: PaginatedTableData<T>) => void;
41+
keepCache?: boolean;
4142
}
4243

4344
const DEFAULT_PAGINATION_LIMIT = 20;
@@ -46,7 +47,7 @@ export const PaginatedTable = <T, F>({
4647
limit: chunkSize = DEFAULT_PAGINATION_LIMIT,
4748
initialEntitiesCount,
4849
fetchData,
49-
filters,
50+
filters: rawFilters,
5051
tableName,
5152
columns,
5253
getRowClassName,
@@ -59,6 +60,7 @@ export const PaginatedTable = <T, F>({
5960
renderEmptyDataMessage,
6061
containerClassName,
6162
onDataFetched,
63+
keepCache = true,
6264
}: PaginatedTableProps<T, F>) => {
6365
const initialTotal = initialEntitiesCount || 0;
6466
const initialFound = initialEntitiesCount || 1;
@@ -78,6 +80,13 @@ export const PaginatedTable = <T, F>({
7880
chunkSize,
7981
});
8082

83+
// this prevent situation when filters are new, but active chunks is not yet recalculated (it will be done to the next rendrer, so we bring filters change on the next render too)
84+
const [filters, setFilters] = React.useState(rawFilters);
85+
86+
React.useEffect(() => {
87+
setFilters(rawFilters);
88+
}, [rawFilters]);
89+
8190
const lastChunkSize = React.useMemo(() => {
8291
// If foundEntities = 0, there will only first chunk
8392
// Display it with 1 row, to display empty data message
@@ -107,7 +116,7 @@ export const PaginatedTable = <T, F>({
107116
if (parentRef?.current) {
108117
parentRef.current.scrollTo(0, 0);
109118
}
110-
}, [filters, initialFound, initialTotal, parentRef]);
119+
}, [rawFilters, initialFound, initialTotal, parentRef]);
111120

112121
const renderChunks = () => {
113122
return activeChunks.map((isActive, index) => (
@@ -127,6 +136,7 @@ export const PaginatedTable = <T, F>({
127136
renderEmptyDataMessage={renderEmptyDataMessage}
128137
onDataFetched={handleDataFetched}
129138
isActive={isActive}
139+
keepCache={keepCache}
130140
/>
131141
));
132142
};

src/components/PaginatedTable/TableChunk.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ interface TableChunkProps<T, F> {
3737
renderErrorMessage?: RenderErrorMessage;
3838
renderEmptyDataMessage?: RenderEmptyDataMessage;
3939
onDataFetched: (data?: PaginatedTableData<T>) => void;
40+
41+
keepCache?: boolean;
4042
}
4143

4244
// Memoisation prevents chunks rerenders that could cause perfomance issues on big tables
@@ -55,6 +57,7 @@ export const TableChunk = typedMemo(function TableChunk<T, F>({
5557
renderEmptyDataMessage,
5658
onDataFetched,
5759
isActive,
60+
keepCache,
5861
}: TableChunkProps<T, F>) {
5962
const [isTimeoutActive, setIsTimeoutActive] = React.useState(true);
6063
const [autoRefreshInterval] = useAutoRefreshInterval();
@@ -74,6 +77,7 @@ export const TableChunk = typedMemo(function TableChunk<T, F>({
7477
tableDataApi.useFetchTableChunkQuery(queryParams, {
7578
skip: isTimeoutActive || !isActive,
7679
pollingInterval: autoRefreshInterval,
80+
refetchOnMountOrArgChange: !keepCache,
7781
});
7882

7983
const {currentData, error} = tableDataApi.endpoints.fetchTableChunk.useQueryState(queryParams);

src/components/PaginatedTable/TableHead.tsx

+28-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import React from 'react';
22

3+
import type {HelpMarkProps} from '@gravity-ui/uikit';
4+
import {HelpMark} from '@gravity-ui/uikit';
5+
36
import {ResizeHandler} from './ResizeHandler';
47
import {
58
ASCENDING,
@@ -9,7 +12,14 @@ import {
912
DESCENDING,
1013
} from './constants';
1114
import {b} from './shared';
12-
import type {Column, HandleTableColumnsResize, OnSort, SortOrderType, SortParams} from './types';
15+
import type {
16+
AlignType,
17+
Column,
18+
HandleTableColumnsResize,
19+
OnSort,
20+
SortOrderType,
21+
SortParams,
22+
} from './types';
1323

1424
// Icon similar to original DataTable icons to keep the same tables across diferent pages and tabs
1525
const SortIcon = ({order}: {order?: SortOrderType}) => {
@@ -43,6 +53,12 @@ const ColumnSortIcon = ({sortOrder, sortable, defaultSortOrder}: ColumnSortIconP
4353
}
4454
};
4555

56+
const columnAlignToHelpMarkPlacement: Record<AlignType, Required<HelpMarkProps['placement']>> = {
57+
left: 'right',
58+
right: 'left',
59+
center: 'right',
60+
};
61+
4662
interface TableHeadCellProps<T> {
4763
column: Column<T>;
4864
resizeable?: boolean;
@@ -115,7 +131,17 @@ export const TableHeadCell = <T,>({
115131
}
116132
}}
117133
>
118-
<div className={b('head-cell-content')}>{content}</div>
134+
<div className={b('head-cell-content-container')}>
135+
<div className={b('head-cell-content')}>{content}</div>
136+
{column.note && (
137+
<HelpMark
138+
placement={columnAlignToHelpMarkPlacement[column.align]}
139+
className={b('head-cell-note')}
140+
>
141+
{column.note}
142+
</HelpMark>
143+
)}
144+
</div>
119145
<ColumnSortIcon
120146
sortOrder={sortOrder}
121147
sortable={column.sortable}

src/components/PaginatedTable/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export type HandleTableColumnsResize = (columnId: string, width: number) => void
2525

2626
export interface Column<T> {
2727
name: string;
28+
note?: string;
2829
header?: React.ReactNode;
2930
className?: string;
3031
sortable?: boolean;

src/components/PaginatedTable/useScrollBasedChunks.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,18 @@ export const useScrollBasedChunks = ({
5151
Math.floor(visibleEnd / rowHeight / chunkSize) + overscanCount,
5252
Math.max(chunksCount - 1, 0),
5353
);
54-
5554
return {start, end};
5655
}, [parentRef, tableRef, rowHeight, chunkSize, overscanCount, chunksCount]);
5756

57+
React.useEffect(() => {
58+
const newRange = calculateVisibleRange();
59+
60+
if (newRange) {
61+
setStartChunk(newRange.start);
62+
setEndChunk(newRange.end);
63+
}
64+
}, [chunksCount, calculateVisibleRange]);
65+
5866
const handleScroll = React.useCallback(() => {
5967
const newRange = calculateVisibleRange();
6068
if (newRange) {

src/components/Search/Search.tsx

+6-29
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import React from 'react';
22

3-
import {TextInput} from '@gravity-ui/uikit';
4-
53
import {cn} from '../../utils/cn';
4+
import {DebouncedInput} from '../DebouncedInput/DebouncedTextInput';
65

76
import './Search.scss';
87

@@ -23,43 +22,21 @@ export const Search = ({
2322
value = '',
2423
width,
2524
className,
26-
debounce = 200,
25+
debounce,
2726
placeholder,
2827
inputRef,
2928
}: SearchProps) => {
30-
const [searchValue, setSearchValue] = React.useState<string>(value);
31-
32-
const timer = React.useRef<number>();
33-
34-
React.useEffect(() => {
35-
setSearchValue((prevValue) => {
36-
if (prevValue !== value) {
37-
return value;
38-
}
39-
40-
return prevValue;
41-
});
42-
}, [value]);
43-
44-
const onSearchValueChange = (newValue: string) => {
45-
setSearchValue(newValue);
46-
47-
window.clearTimeout(timer.current);
48-
timer.current = window.setTimeout(() => {
49-
onChange?.(newValue);
50-
}, debounce);
51-
};
52-
5329
return (
54-
<TextInput
30+
<DebouncedInput
31+
debounce={debounce}
5532
hasClear
5633
autoFocus
5734
controlRef={inputRef}
5835
style={{width}}
5936
className={b(null, className)}
6037
placeholder={placeholder}
61-
value={searchValue}
62-
onUpdate={onSearchValueChange}
38+
value={value}
39+
onUpdate={onChange}
6340
/>
6441
);
6542
};

0 commit comments

Comments
 (0)