Skip to content

feat(TopicData): add tab for topic data #2145

New issue

Have a question about this project? No Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “No Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? No Sign in to your account

Merged
merged 21 commits into from
Apr 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/components/DebouncedInput/DebouncedTextInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type {TextInputProps} from '@gravity-ui/uikit';
import {TextInput} from '@gravity-ui/uikit';

import {useDebouncedValue} from '../../utils/hooks/useDebouncedValue';

interface DebouncedInputProps extends TextInputProps {
debounce?: number;
}

export const DebouncedInput = ({
onUpdate,
value = '',
debounce = 200,
...rest
}: DebouncedInputProps) => {
const [currentValue, handleUpdate] = useDebouncedValue<string>({value, onUpdate, debounce});

return <TextInput value={currentValue} onUpdate={handleUpdate} {...rest} />;
};
3 changes: 3 additions & 0 deletions src/components/EmptyState/EmptyState.scss
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@

margin: 0 auto;
}
&_position_left {
margin: unset;
}
}

&__image {
Expand Down
17 changes: 4 additions & 13 deletions src/components/MetricChart/getDefaultDataFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {formatBytes} from '../../utils/bytesParsers';
import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants';
import {roundToPrecision} from '../../utils/dataFormatters/dataFormatters';
import {formatToMs} from '../../utils/timeParsers';
import {isNumeric} from '../../utils/utils';
import {safeParseNumber} from '../../utils/utils';

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

Expand All @@ -28,27 +28,18 @@ function formatChartValueToMs(value: ChartValue) {
if (value === null) {
return EMPTY_DATA_PLACEHOLDER;
}
return formatToMs(roundToPrecision(convertToNumber(value), 2));
return formatToMs(roundToPrecision(safeParseNumber(value), 2));
}

function formatChartValueToSize(value: ChartValue) {
if (value === null) {
return EMPTY_DATA_PLACEHOLDER;
}
return formatBytes({value: convertToNumber(value), precision: 3});
return formatBytes({value: safeParseNumber(value), precision: 3});
}
function formatChartValueToPercent(value: ChartValue) {
if (value === null) {
return EMPTY_DATA_PLACEHOLDER;
}
return Math.round(convertToNumber(value) * 100) + '%';
}

// Numeric values expected, not numeric value should be displayd as 0
function convertToNumber(value: unknown): number {
if (isNumeric(value)) {
return Number(value);
}

return 0;
return Math.round(safeParseNumber(value) * 100) + '%';
}
3 changes: 3 additions & 0 deletions src/components/MultilineTableHeader/MultilineTableHeader.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.ydb-multiline-table-header {
white-space: normal;
}
16 changes: 16 additions & 0 deletions src/components/MultilineTableHeader/MultilineTableHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {cn} from '../../utils/cn';

import './MultilineTableHeader.scss';

const b = cn('ydb-multiline-table-header');

interface MultilineTableHeaderProps {
title?: string;
}

export function MultilineTableHeader({title}: MultilineTableHeaderProps) {
if (!title) {
return null;
}
return <div className={b()}>{title}</div>;
}
45 changes: 32 additions & 13 deletions src/components/PaginatedTable/PaginatedTable.scss
Original file line number Diff line number Diff line change
Expand Up @@ -79,50 +79,69 @@
display: flex;
flex-direction: row;
align-items: center;
gap: var(--g-spacing-2);

width: 100%;
max-width: 100%;
padding: var(--paginated-table-cell-vertical-padding)
var(--paginated-table-cell-horizontal-padding);

font-weight: bold;
cursor: default;
&_align {
&_left {
justify-content: left;

text-align: left;
}
&_center {
justify-content: center;

text-align: center;
}
&_right {
justify-content: right;

text-align: right;

#{$block}__head-cell-content-container {
flex-direction: row-reverse;
}
}
}
}

&__head-cell {
gap: 8px;

font-weight: bold;
cursor: default;

&_sortable {
cursor: pointer;
&__head-cell_sortable {
cursor: pointer;

&#{$block}__head-cell_align_right {
flex-direction: row-reverse;
}
&#{$block}__head-cell_align_right {
flex-direction: row-reverse;
}
}

&__head-cell-note {
display: flex;
}

// Separate head cell content class for correct text ellipsis overflow
&__head-cell-content {
overflow: hidden;

width: min-content;

white-space: nowrap;
text-overflow: ellipsis;
}

&__head-cell-content-container {
display: inline-flex;
overflow: hidden;
gap: var(--g-spacing-1);

.g-help-mark__button {
display: inline-flex;
align-items: center;
}
}

&__row-cell {
display: table-cell;
overflow-x: hidden;
Expand Down
14 changes: 12 additions & 2 deletions src/components/PaginatedTable/PaginatedTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export interface PaginatedTableProps<T, F> {
renderErrorMessage?: RenderErrorMessage;
containerClassName?: string;
onDataFetched?: (data: PaginatedTableData<T>) => void;
keepCache?: boolean;
}

const DEFAULT_PAGINATION_LIMIT = 20;
Expand All @@ -46,7 +47,7 @@ export const PaginatedTable = <T, F>({
limit: chunkSize = DEFAULT_PAGINATION_LIMIT,
initialEntitiesCount,
fetchData,
filters,
filters: rawFilters,
tableName,
columns,
getRowClassName,
Expand All @@ -59,6 +60,7 @@ export const PaginatedTable = <T, F>({
renderEmptyDataMessage,
containerClassName,
onDataFetched,
keepCache = true,
}: PaginatedTableProps<T, F>) => {
const initialTotal = initialEntitiesCount || 0;
const initialFound = initialEntitiesCount || 1;
Expand All @@ -78,6 +80,13 @@ export const PaginatedTable = <T, F>({
chunkSize,
});

// 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)
const [filters, setFilters] = React.useState(rawFilters);

React.useEffect(() => {
setFilters(rawFilters);
}, [rawFilters]);

const lastChunkSize = React.useMemo(() => {
// If foundEntities = 0, there will only first chunk
// Display it with 1 row, to display empty data message
Expand Down Expand Up @@ -107,7 +116,7 @@ export const PaginatedTable = <T, F>({
if (parentRef?.current) {
parentRef.current.scrollTo(0, 0);
}
}, [filters, initialFound, initialTotal, parentRef]);
}, [rawFilters, initialFound, initialTotal, parentRef]);

const renderChunks = () => {
return activeChunks.map((isActive, index) => (
Expand All @@ -127,6 +136,7 @@ export const PaginatedTable = <T, F>({
renderEmptyDataMessage={renderEmptyDataMessage}
onDataFetched={handleDataFetched}
isActive={isActive}
keepCache={keepCache}
/>
));
};
Expand Down
4 changes: 4 additions & 0 deletions src/components/PaginatedTable/TableChunk.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ interface TableChunkProps<T, F> {
renderErrorMessage?: RenderErrorMessage;
renderEmptyDataMessage?: RenderEmptyDataMessage;
onDataFetched: (data?: PaginatedTableData<T>) => void;

keepCache?: boolean;
}

// Memoisation prevents chunks rerenders that could cause perfomance issues on big tables
Expand All @@ -55,6 +57,7 @@ export const TableChunk = typedMemo(function TableChunk<T, F>({
renderEmptyDataMessage,
onDataFetched,
isActive,
keepCache,
}: TableChunkProps<T, F>) {
const [isTimeoutActive, setIsTimeoutActive] = React.useState(true);
const [autoRefreshInterval] = useAutoRefreshInterval();
Expand All @@ -74,6 +77,7 @@ export const TableChunk = typedMemo(function TableChunk<T, F>({
tableDataApi.useFetchTableChunkQuery(queryParams, {
skip: isTimeoutActive || !isActive,
pollingInterval: autoRefreshInterval,
refetchOnMountOrArgChange: !keepCache,
});

const {currentData, error} = tableDataApi.endpoints.fetchTableChunk.useQueryState(queryParams);
Expand Down
30 changes: 28 additions & 2 deletions src/components/PaginatedTable/TableHead.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import React from 'react';

import type {HelpMarkProps} from '@gravity-ui/uikit';
import {HelpMark} from '@gravity-ui/uikit';

import {ResizeHandler} from './ResizeHandler';
import {
ASCENDING,
Expand All @@ -9,7 +12,14 @@ import {
DESCENDING,
} from './constants';
import {b} from './shared';
import type {Column, HandleTableColumnsResize, OnSort, SortOrderType, SortParams} from './types';
import type {
AlignType,
Column,
HandleTableColumnsResize,
OnSort,
SortOrderType,
SortParams,
} from './types';

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

const columnAlignToHelpMarkPlacement: Record<AlignType, Required<HelpMarkProps['placement']>> = {
left: 'right',
right: 'left',
center: 'right',
};

interface TableHeadCellProps<T> {
column: Column<T>;
resizeable?: boolean;
Expand Down Expand Up @@ -115,7 +131,17 @@ export const TableHeadCell = <T,>({
}
}}
>
<div className={b('head-cell-content')}>{content}</div>
<div className={b('head-cell-content-container')}>
<div className={b('head-cell-content')}>{content}</div>
{column.note && (
<HelpMark
placement={columnAlignToHelpMarkPlacement[column.align]}
Copy link
Preview

Copilot AI Apr 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If column.align is undefined or not one of 'left', 'right', or 'center', this mapping will return undefined. Consider providing a default placement value to prevent potential runtime issues.

Suggested change
placement={columnAlignToHelpMarkPlacement[column.align]}
placement={columnAlignToHelpMarkPlacement[column.align] ?? 'top'}

Copilot uses AI. Check for mistakes.

className={b('head-cell-note')}
>
{column.note}
</HelpMark>
)}
</div>
<ColumnSortIcon
sortOrder={sortOrder}
sortable={column.sortable}
Expand Down
1 change: 1 addition & 0 deletions src/components/PaginatedTable/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export type HandleTableColumnsResize = (columnId: string, width: number) => void

export interface Column<T> {
name: string;
note?: string;
header?: React.ReactNode;
className?: string;
sortable?: boolean;
Expand Down
10 changes: 9 additions & 1 deletion src/components/PaginatedTable/useScrollBasedChunks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,18 @@ export const useScrollBasedChunks = ({
Math.floor(visibleEnd / rowHeight / chunkSize) + overscanCount,
Math.max(chunksCount - 1, 0),
);

return {start, end};
}, [parentRef, tableRef, rowHeight, chunkSize, overscanCount, chunksCount]);

React.useEffect(() => {
const newRange = calculateVisibleRange();

if (newRange) {
setStartChunk(newRange.start);
setEndChunk(newRange.end);
}
}, [chunksCount, calculateVisibleRange]);

const handleScroll = React.useCallback(() => {
const newRange = calculateVisibleRange();
if (newRange) {
Expand Down
35 changes: 6 additions & 29 deletions src/components/Search/Search.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import React from 'react';

import {TextInput} from '@gravity-ui/uikit';

import {cn} from '../../utils/cn';
import {DebouncedInput} from '../DebouncedInput/DebouncedTextInput';

import './Search.scss';

Expand All @@ -23,43 +22,21 @@ export const Search = ({
value = '',
width,
className,
debounce = 200,
debounce,
placeholder,
inputRef,
}: SearchProps) => {
const [searchValue, setSearchValue] = React.useState<string>(value);

const timer = React.useRef<number>();

React.useEffect(() => {
setSearchValue((prevValue) => {
if (prevValue !== value) {
return value;
}

return prevValue;
});
}, [value]);

const onSearchValueChange = (newValue: string) => {
setSearchValue(newValue);

window.clearTimeout(timer.current);
timer.current = window.setTimeout(() => {
onChange?.(newValue);
}, debounce);
};

return (
<TextInput
<DebouncedInput
debounce={debounce}
hasClear
autoFocus
controlRef={inputRef}
style={{width}}
className={b(null, className)}
placeholder={placeholder}
value={searchValue}
onUpdate={onSearchValueChange}
value={value}
onUpdate={onChange}
/>
);
};
Loading
Loading