Skip to content

Commit 6746821

Browse files
committed
feat(TopicData): add topic message details in Drawer
1 parent 544954e commit 6746821

File tree

29 files changed

+869
-217
lines changed

29 files changed

+869
-217
lines changed

src/components/EnableFullscreenButton/EnableFullscreenButton.tsx

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,22 @@
11
import {SquareDashed} from '@gravity-ui/icons';
2+
import type {ButtonView} from '@gravity-ui/uikit';
23
import {Button, Icon} from '@gravity-ui/uikit';
34

45
import {enableFullscreen} from '../../store/reducers/fullscreen';
56
import {useTypedDispatch} from '../../utils/hooks';
67

78
interface EnableFullscreenButtonProps {
89
disabled?: boolean;
10+
view?: ButtonView;
911
}
1012

11-
function EnableFullscreenButton({disabled}: EnableFullscreenButtonProps) {
13+
function EnableFullscreenButton({disabled, view = 'flat-secondary'}: EnableFullscreenButtonProps) {
1214
const dispatch = useTypedDispatch();
1315
const onEnableFullscreen = () => {
1416
dispatch(enableFullscreen());
1517
};
1618
return (
17-
<Button
18-
onClick={onEnableFullscreen}
19-
view="flat-secondary"
20-
disabled={disabled}
21-
title="Fullscreen"
22-
>
19+
<Button onClick={onEnableFullscreen} view={view} disabled={disabled} title="Fullscreen">
2320
<Icon data={SquareDashed} />
2421
</Button>
2522
);

src/components/JsonViewer/JsonViewer.tsx

+7-2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ interface JsonViewerCommonProps {
3333
tableSettings?: DT100.Settings;
3434
search?: boolean;
3535
collapsedInitially?: boolean;
36+
maxValueWidth?: number;
37+
toolbarClassName?: string;
3638
}
3739

3840
interface JsonViewerProps extends JsonViewerCommonProps {
@@ -60,7 +62,7 @@ const SETTINGS: DT100.Settings = {
6062
displayIndices: false,
6163
dynamicRender: true,
6264
sortable: false,
63-
dynamicRenderMinSize: 100,
65+
dynamicRenderMinSize: 50,
6466
};
6567

6668
function getCollapsedState(value: UnipikaValue) {
@@ -114,6 +116,8 @@ function JsonViewerComponent({
114116
search = true,
115117
extraTools,
116118
collapsedInitially,
119+
maxValueWidth = 100,
120+
toolbarClassName,
117121
}: JsonViewerComponentProps) {
118122
const [caseSensitiveSearch, setCaseSensitiveSearch] = useSetting(
119123
CASE_SENSITIVE_JSON_SEARCH,
@@ -162,6 +166,7 @@ function JsonViewerComponent({
162166
filter={filter}
163167
showFullText={onShowFullText}
164168
index={index}
169+
maxValueWidth={maxValueWidth}
165170
/>
166171
);
167172
};
@@ -295,7 +300,7 @@ function JsonViewerComponent({
295300

296301
const renderToolbar = () => {
297302
return (
298-
<Flex gap={2} wrap="nowrap" className={block('toolbar')}>
303+
<Flex gap={2} wrap="nowrap" className={block('toolbar', toolbarClassName)}>
299304
<Flex gap={1} wrap="nowrap">
300305
<ActionTooltip title={i18n('action_expand-all')}>
301306
<Button onClick={onExpandAll} view="flat-secondary">

src/components/JsonViewer/components/Cell.tsx

+16-5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ interface CellProps {
2323
filter?: string;
2424
index: number;
2525
showFullText: (index: number) => void;
26+
maxValueWidth?: number;
2627
}
2728

2829
export function Cell(props: CellProps) {
@@ -69,6 +70,7 @@ export function Cell(props: CellProps) {
6970
matched={matched?.valueMatch}
7071
filter={filter}
7172
showFullText={handleShowFullText}
73+
maxValueWidth={props.maxValueWidth}
7274
/>
7375
)}
7476
{collapsed && depth === undefined && <span className={'unipika'}>...</span>}
@@ -97,6 +99,7 @@ function Key(props: KeyProps) {
9799

98100
interface ValueProps extends KeyProps {
99101
showFullText?: () => void;
102+
maxValueWidth?: number;
100103
}
101104

102105
function Value(props: ValueProps) {
@@ -109,16 +112,24 @@ function Value(props: ValueProps) {
109112

110113
function renderValueWithFilter(props: ValueProps, className: string) {
111114
if ('string' === props.text?.$type) {
112-
return renderStringWithFilter(props, className, 100);
115+
return renderStringWithFilter(props, className);
113116
}
114117
return renderWithFilter(props, block('value'));
115118
}
116119

117-
function renderStringWithFilter(props: ValueProps, className: string, maxWidth = Infinity) {
118-
const {text, settings = defaultUnipikaSettings, matched = [], filter, showFullText} = props;
119-
const tmp = unipika.format(text, {...settings, asHTML: false});
120+
function renderStringWithFilter(props: ValueProps, className: string) {
121+
const {
122+
text,
123+
settings = defaultUnipikaSettings,
124+
matched = [],
125+
filter,
126+
showFullText,
127+
maxValueWidth = Infinity,
128+
} = props;
129+
130+
const tmp = unipika.format(text, {...settings, maxStringSize: 10, asHTML: false});
120131
const length = tmp.length;
121-
const visible = tmp.substring(1, Math.min(length - 1, maxWidth + 1));
132+
const visible = tmp.substring(1, Math.min(length - 1, maxValueWidth + 1));
122133
const truncated = visible.length < tmp.length - 2;
123134
let hasHiddenMatch = false;
124135
if (truncated) {

src/components/ShortyString/ShortyString.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
.kv-shorty-string {
22
&__toggle {
3-
margin-left: 2em;
3+
margin-left: 1em;
44

55
font-size: 0.85em;
66
}

src/containers/Tenant/Diagnostics/Partitions/PartitionsControls/PartitionsControls.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export const PartitionsControls = ({
5656
connectionHost,
5757
} = partition;
5858

59-
const isPartitionIdMatch = partitionIdRe.test(partitionId);
59+
const isPartitionIdMatch = partitionIdRe.test(String(partitionId));
6060

6161
const otherValues = [
6262
readerName,

src/containers/Tenant/Diagnostics/Partitions/columns/columns.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export const allColumns: Column<PreparedPartitionDataWithHosts>[] = [
4545
),
4646
sortAccessor: (row) => isNumeric(row.partitionId) && Number(row.partitionId),
4747
align: DataTable.LEFT,
48-
render: ({row}) => <PartitionId id={row.partitionId} />,
48+
render: ({row}) => <PartitionId id={String(row.partitionId)} />,
4949
},
5050
{
5151
name: PARTITIONS_COLUMNS_IDS.STORE_SIZE,

src/containers/Tenant/Diagnostics/TopicData/TopicData.scss

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
@use '../../../../styles/mixins.scss';
2+
13
.ydb-diagnostics-topic-data {
24
&__partition-select {
35
min-width: 150px;
@@ -24,5 +26,8 @@
2426
background: var(--g-color-base-selection-hover) !important;
2527
}
2628
}
29+
&_removed {
30+
color: var(--g-color-text-secondary);
31+
}
2732
}
2833
}

src/containers/Tenant/Diagnostics/TopicData/TopicData.tsx

+87-30
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import {NoSearchResults} from '@gravity-ui/illustrations';
44
import {skipToken} from '@reduxjs/toolkit/query';
55
import {isNil} from 'lodash';
66

7+
import {DrawerWrapper} from '../../../../components/Drawer';
8+
import EnableFullscreenButton from '../../../../components/EnableFullscreenButton/EnableFullscreenButton';
9+
import Fullscreen from '../../../../components/Fullscreen/Fullscreen';
710
import type {RenderControls} from '../../../../components/PaginatedTable';
811
import {
912
DEFAULT_TABLE_ROW_HEIGHT,
@@ -19,6 +22,7 @@ import {safeParseNumber} from '../../../../utils/utils';
1922
import {EmptyFilter} from '../../../Storage/EmptyFilter/EmptyFilter';
2023

2124
import {TopicDataControls} from './TopicDataControls/TopicDataControls';
25+
import {TopicMessageDetails} from './TopicMessageDetails/TopicMessageDetails';
2226
import {
2327
DEFAULT_TOPIC_DATA_COLUMNS,
2428
REQUIRED_TOPIC_DATA_COLUMNS,
@@ -43,6 +47,8 @@ interface TopicDataProps {
4347
parentRef: React.RefObject<HTMLElement>;
4448
}
4549

50+
const columns = getAllColumns();
51+
4652
export function TopicData({parentRef, path, database}: TopicDataProps) {
4753
const [autoRefreshInterval] = useAutoRefreshInterval();
4854
const [startOffset, setStartOffset] = React.useState<number>();
@@ -61,9 +67,11 @@ export function TopicData({parentRef, path, database}: TopicDataProps) {
6167
selectedOffset,
6268
startTimestamp,
6369
topicDataFilter,
70+
activeOffset,
6471
handleSelectedOffsetChange,
6572
handleStartTimestampChange,
6673
handleSelectedPartitionChange,
74+
handleActiveOffsetChange,
6775
} = useTopicDataQueryParams();
6876

6977
React.useEffect(() => {
@@ -114,12 +122,15 @@ export function TopicData({parentRef, path, database}: TopicDataProps) {
114122

115123
React.useEffect(() => {
116124
if (partitions && partitions.length && isNil(selectedPartition)) {
117-
handleSelectedPartitionChange(partitions[0].partitionId);
125+
const firstPartitionId = partitions[0].partitionId;
126+
handleSelectedPartitionChange(
127+
isNil(firstPartitionId) ? undefined : String(firstPartitionId),
128+
);
118129
}
119130
}, [partitions, selectedPartition, handleSelectedPartitionChange]);
120131

121132
const {columnsToShow, columnsToSelect, setColumns} = useSelectedColumns(
122-
getAllColumns(),
133+
columns,
123134
TOPIC_DATA_SELECTED_COLUMNS_LS_KEY,
124135
TOPIC_DATA_COLUMNS_TITLES,
125136
DEFAULT_TOPIC_DATA_COLUMNS,
@@ -172,19 +183,28 @@ export function TopicData({parentRef, path, database}: TopicDataProps) {
172183
[baseOffset, parentRef],
173184
);
174185

186+
//this variable is used to scroll to active offset the very first time on open page
187+
const initialActiveOffset = React.useMemo(() => activeOffset, []);
188+
175189
React.useEffect(() => {
176190
if (isFetching) {
177191
return;
178192
}
179-
const messages = currentData?.Messages;
180-
if (messages?.length) {
181-
const messageOffset = safeParseNumber(messages[0].Offset);
182-
//scroll when table is already rendered and calculated it's state
183-
setTimeout(() => {
184-
scrollToOffset(messageOffset);
185-
}, 0);
193+
194+
let currentOffset: number | undefined;
195+
196+
if (isNil(initialActiveOffset)) {
197+
const messages = currentData?.Messages;
198+
if (messages?.length) {
199+
currentOffset = safeParseNumber(messages[0].Offset);
200+
}
201+
} else {
202+
currentOffset = safeParseNumber(initialActiveOffset);
203+
}
204+
if (!isNil(currentOffset)) {
205+
scrollToOffset(currentOffset);
186206
}
187-
}, [currentData, isFetching, scrollToOffset]);
207+
}, [currentData, isFetching, scrollToOffset, initialActiveOffset]);
188208

189209
const renderControls: RenderControls = () => {
190210
return (
@@ -222,29 +242,66 @@ export function TopicData({parentRef, path, database}: TopicDataProps) {
222242
[baseOffset],
223243
);
224244

245+
const closeDrawer = React.useCallback(() => {
246+
handleActiveOffsetChange(undefined);
247+
}, [handleActiveOffsetChange]);
248+
249+
const renderDrawerContent = React.useCallback(() => {
250+
return (
251+
<Fullscreen>
252+
<TopicMessageDetails database={database} path={path} />
253+
</Fullscreen>
254+
);
255+
}, [database, path]);
256+
225257
return (
226258
!isNil(baseOffset) &&
227259
!isNil(baseEndOffset) && (
228-
<ResizeablePaginatedTable
229-
columnsWidthLSKey={TOPIC_DATA_COLUMNS_WIDTH_LS_KEY}
230-
parentRef={parentRef}
231-
columns={columnsToShow}
232-
fetchData={getTopicData}
233-
initialEntitiesCount={baseEndOffset - baseOffset}
234-
limit={TOPIC_DATA_FETCH_LIMIT}
235-
renderControls={renderControls}
236-
renderErrorMessage={renderPaginatedTableErrorMessage}
237-
renderEmptyDataMessage={renderEmptyDataMessage}
238-
filters={tableFilters}
239-
tableName="topicData"
240-
rowHeight={DEFAULT_TABLE_ROW_HEIGHT}
241-
keepCache={false}
242-
getRowClassName={(row) => {
243-
return b('row', {
244-
active: Boolean(selectedOffset && String(row.Offset) === selectedOffset),
245-
});
246-
}}
247-
/>
260+
<DrawerWrapper
261+
isDrawerVisible={!isNil(activeOffset)}
262+
onCloseDrawer={closeDrawer}
263+
renderDrawerContent={renderDrawerContent}
264+
drawerId="topic-data-details"
265+
storageKey="topic-data-details-drawer-width"
266+
detectClickOutside
267+
isPercentageWidth
268+
drawerControls={[
269+
{type: 'copyLink', link: window.location.href},
270+
{
271+
type: 'custom',
272+
node: <EnableFullscreenButton disabled={Boolean(error)} view="flat" />,
273+
key: 'fullscreen',
274+
},
275+
{type: 'close'},
276+
]}
277+
title={i18n('label_message')}
278+
headerClassName={b('drawer-header')}
279+
>
280+
<ResizeablePaginatedTable
281+
columnsWidthLSKey={TOPIC_DATA_COLUMNS_WIDTH_LS_KEY}
282+
parentRef={parentRef}
283+
columns={columnsToShow}
284+
fetchData={getTopicData}
285+
initialEntitiesCount={baseEndOffset - baseOffset}
286+
limit={TOPIC_DATA_FETCH_LIMIT}
287+
renderControls={renderControls}
288+
renderErrorMessage={renderPaginatedTableErrorMessage}
289+
renderEmptyDataMessage={renderEmptyDataMessage}
290+
filters={tableFilters}
291+
tableName="topicData"
292+
rowHeight={DEFAULT_TABLE_ROW_HEIGHT}
293+
keepCache={false}
294+
getRowClassName={(row) => {
295+
return b('row', {
296+
active: Boolean(
297+
String(row.Offset) === selectedOffset ||
298+
String(row.Offset) === activeOffset,
299+
),
300+
removed: row.removed,
301+
});
302+
}}
303+
/>
304+
</DrawerWrapper>
248305
)
249306
);
250307
}

src/containers/Tenant/Diagnostics/TopicData/TopicDataControls/TopicDataControls.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ export function TopicDataControls({
5959
} = useTopicDataQueryParams();
6060

6161
const partitionsToSelect = partitions?.map(({partitionId}) => ({
62-
content: partitionId,
63-
value: partitionId,
62+
content: String(partitionId),
63+
value: String(partitionId),
6464
}));
6565

6666
const handleSelectedPartitionChange = React.useCallback(

0 commit comments

Comments
 (0)