Skip to content

Commit cb409bd

Browse files
committed
feat: design query tab
1 parent c9191e2 commit cb409bd

File tree

17 files changed

+300
-101
lines changed

17 files changed

+300
-101
lines changed

src/components/DateRange/DateRange.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
.date-range {
22
&__range-input {
33
&_s {
4-
width: 200px;
4+
width: 130px;
55
}
66

77
&_m {

src/components/DateRange/DateRange.tsx

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import React from 'react';
22

3-
import type {RelativeRangeDatePickerProps} from '@gravity-ui/date-components';
3+
import type {
4+
RelativeRangeDatePickerProps,
5+
RelativeRangeDatePickerValue,
6+
} from '@gravity-ui/date-components';
47
import {RelativeRangeDatePicker} from '@gravity-ui/date-components';
58

69
import {cn} from '../../utils/cn';
@@ -19,23 +22,13 @@ export interface DateRangeValues {
1922
to?: string;
2023
}
2124

22-
const DEFAULT_VALUE = {
23-
start: {
24-
value: 'now-1h',
25-
type: 'relative',
26-
},
27-
end: {
28-
value: 'now',
29-
type: 'relative',
30-
},
31-
} as const;
32-
3325
interface DateRangeProps extends DateRangeValues {
3426
className?: string;
27+
defaultValue?: RelativeRangeDatePickerValue;
3528
onChange?: (value: DateRangeValues) => void;
3629
}
3730

38-
export const DateRange = ({from, to, className, onChange}: DateRangeProps) => {
31+
export const DateRange = ({from, to, className, defaultValue, onChange}: DateRangeProps) => {
3932
const handleUpdate = React.useCallback<NonNullable<RelativeRangeDatePickerProps['onUpdate']>>(
4033
(pickerValue) => onChange?.(toDateRangeValues(pickerValue)),
4134
[onChange],
@@ -50,13 +43,14 @@ export const DateRange = ({from, to, className, onChange}: DateRangeProps) => {
5043

5144
// eslint-disable-next-line new-cap
5245
const timeZoneString = Intl.DateTimeFormat().resolvedOptions().timeZone;
46+
const currentValue = value || defaultValue;
5347
return (
5448
<div className={b(null, className)}>
5549
<RelativeRangeDatePicker
5650
withPresets
57-
className={b('range-input', {[getdatePickerSize(value)]: true})}
51+
className={b('range-input', {[getdatePickerSize(currentValue)]: true})}
5852
timeZone={timeZoneString}
59-
value={value || DEFAULT_VALUE}
53+
value={currentValue}
6054
allowNullableValues
6155
size="m"
6256
format={i18n('date-time-format')}

src/components/ResizeableDataTable/ResizeableDataTable.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,9 @@
55

66
// padding for easier resize of the last column
77
padding-right: 20px;
8+
9+
&__row-skeleton {
10+
width: 100%;
11+
height: 50%;
12+
}
813
}

src/components/ResizeableDataTable/ResizeableDataTable.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import type {DataTableProps, Settings} from '@gravity-ui/react-data-table';
1+
import type {Column, DataTableProps, Settings} from '@gravity-ui/react-data-table';
22
import DataTable, {updateColumnsWidth} from '@gravity-ui/react-data-table';
3+
import {Skeleton} from '@gravity-ui/uikit';
34

45
import {cn} from '../../utils/cn';
56
import {useTableResize} from '../../utils/hooks/useTableResize';
@@ -11,18 +12,28 @@ const b = cn('ydb-resizeable-data-table');
1112
export interface ResizeableDataTableProps<T> extends Omit<DataTableProps<T>, 'theme' | 'onResize'> {
1213
columnsWidthLSKey?: string;
1314
wrapperClassName?: string;
15+
loading?: boolean;
1416
}
1517

1618
export function ResizeableDataTable<T>({
1719
columnsWidthLSKey,
1820
columns,
1921
settings,
2022
wrapperClassName,
23+
loading,
2124
...props
2225
}: ResizeableDataTableProps<T>) {
2326
const [tableColumnsWidth, setTableColumnsWidth] = useTableResize(columnsWidthLSKey);
2427

25-
const updatedColumns = updateColumnsWidth(columns, tableColumnsWidth);
28+
// If loading is true, override the render method of each column to display a Skeleton
29+
const processedColumns = loading
30+
? columns.map((column: Column<T>) => ({
31+
...column,
32+
render: () => <Skeleton className={b('row-skeleton')} />,
33+
}))
34+
: columns;
35+
36+
const updatedColumns = updateColumnsWidth(processedColumns, tableColumnsWidth);
2637

2738
const newSettings: Settings = {
2839
...settings,

src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopQueries.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export function TopQueries({tenantName}: TopQueriesProps) {
4545
}, []);
4646

4747
const {currentData, isFetching, error} = topQueriesApi.useGetTopQueriesQuery(
48-
{database: tenantName},
48+
{database: tenantName, timeFrame: 'hour'},
4949
{pollingInterval: autoRefreshInterval},
5050
);
5151

src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import React from 'react';
22

3+
import type {Column} from '@gravity-ui/react-data-table';
4+
35
import {ResponseError} from '../../../../components/Errors/ResponseError';
46
import {ResizeableDataTable} from '../../../../components/ResizeableDataTable/ResizeableDataTable';
57
import {TableWithControlsLayout} from '../../../../components/TableWithControlsLayout/TableWithControlsLayout';
@@ -8,7 +10,6 @@ import type {KeyValueRow} from '../../../../types/api/query';
810
import {useAutoRefreshInterval, useTypedSelector} from '../../../../utils/hooks';
911
import {parseQueryErrorToString} from '../../../../utils/query';
1012

11-
import {getRunningQueriesColumns} from './columns/columns';
1213
import {RUNNING_QUERIES_COLUMNS_WIDTH_LS_KEY} from './columns/constants';
1314
import i18n from './i18n';
1415
import {TOP_QUERIES_TABLE_SETTINGS, useRunningQueriesSort} from './utils';
@@ -17,30 +18,24 @@ interface Props {
1718
database: string;
1819
onRowClick: (query: string) => void;
1920
rowClassName: string;
21+
columns: Column<KeyValueRow>[];
2022
}
2123

22-
export const RunningQueriesData = ({database, onRowClick, rowClassName}: Props) => {
24+
export const RunningQueriesData = ({database, onRowClick, rowClassName, columns}: Props) => {
2325
const [autoRefreshInterval] = useAutoRefreshInterval();
2426
const filters = useTypedSelector((state) => state.executeTopQueries);
2527

2628
const {tableSort, handleTableSort, backendSort} = useRunningQueriesSort();
2729

28-
const {currentData, isFetching, error} = topQueriesApi.useGetRunningQueriesQuery(
29-
{
30-
database,
31-
filters,
32-
sortOrder: backendSort,
33-
},
34-
{pollingInterval: autoRefreshInterval},
35-
);
36-
37-
const loading = isFetching && currentData === undefined;
38-
39-
const data = currentData?.resultSets?.[0].result || [];
40-
41-
const columns = React.useMemo(() => {
42-
return getRunningQueriesColumns();
43-
}, []);
30+
const {currentData, data, isLoading, isFetching, error} =
31+
topQueriesApi.useGetRunningQueriesQuery(
32+
{
33+
database,
34+
filters,
35+
sortOrder: backendSort,
36+
},
37+
{pollingInterval: autoRefreshInterval},
38+
);
4439

4540
const handleRowClick = (row: KeyValueRow) => {
4641
return onRowClick(row.QueryText as string);
@@ -49,12 +44,13 @@ export const RunningQueriesData = ({database, onRowClick, rowClassName}: Props)
4944
return (
5045
<React.Fragment>
5146
{error ? <ResponseError error={parseQueryErrorToString(error)} /> : null}
52-
<TableWithControlsLayout.Table loading={loading}>
47+
<TableWithControlsLayout.Table loading={isLoading}>
5348
<ResizeableDataTable
5449
emptyDataMessage={i18n('no-data')}
5550
columnsWidthLSKey={RUNNING_QUERIES_COLUMNS_WIDTH_LS_KEY}
5651
columns={columns}
57-
data={data}
52+
data={data?.resultSets?.[0].result || []}
53+
loading={isFetching && currentData === undefined}
5854
settings={TOP_QUERIES_TABLE_SETTINGS}
5955
onRowClick={handleRowClick}
6056
rowClassName={() => rowClassName}

src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx

Lines changed: 94 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import React from 'react';
22

3-
import type {RadioButtonOption} from '@gravity-ui/uikit';
4-
import {RadioButton} from '@gravity-ui/uikit';
3+
import type {Column} from '@gravity-ui/react-data-table';
4+
import type {RadioButtonOption, SelectOption} from '@gravity-ui/uikit';
5+
import {RadioButton, Select, TableColumnSetup} from '@gravity-ui/uikit';
56
import {useHistory, useLocation} from 'react-router-dom';
67
import {StringParam, useQueryParam} from 'use-query-params';
78
import {z} from 'zod';
@@ -12,6 +13,7 @@ import {Search} from '../../../../components/Search';
1213
import {TableWithControlsLayout} from '../../../../components/TableWithControlsLayout/TableWithControlsLayout';
1314
import {parseQuery} from '../../../../routes';
1415
import {setTopQueriesFilters} from '../../../../store/reducers/executeTopQueries/executeTopQueries';
16+
import type {TimeFrame} from '../../../../store/reducers/executeTopQueries/types';
1517
import {changeUserInput, setIsDirty} from '../../../../store/reducers/query/query';
1618
import {
1719
TENANT_PAGE,
@@ -20,11 +22,22 @@ import {
2022
} from '../../../../store/reducers/tenant/constants';
2123
import {cn} from '../../../../utils/cn';
2224
import {useTypedDispatch, useTypedSelector} from '../../../../utils/hooks';
25+
import {useSelectedColumns} from '../../../../utils/hooks/useSelectedColumns';
2326
import {useChangeInputWithConfirmation} from '../../../../utils/hooks/withConfirmation/useChangeInputWithConfirmation';
2427
import {TenantTabsGroups, getTenantPath} from '../../TenantPages';
2528

2629
import {RunningQueriesData} from './RunningQueriesData';
2730
import {TopQueriesData} from './TopQueriesData';
31+
import {getRunningQueriesColumns, getTopQueriesColumns} from './columns/columns';
32+
import {
33+
DEFAULT_RUNNING_QUERIES_COLUMNS,
34+
DEFAULT_TOP_QUERIES_COLUMNS,
35+
REQUIRED_RUNNING_QUERIES_COLUMNS,
36+
REQUIRED_TOP_QUERIES_COLUMNS,
37+
RUNNING_QUERIES_SELECTED_COLUMNS_LS_KEY,
38+
TOP_QUERIES_COLUMNS_TITLES,
39+
TOP_QUERIES_SELECTED_COLUMNS_LS_KEY,
40+
} from './columns/constants';
2841
import i18n from './i18n';
2942

3043
import './TopQueries.scss';
@@ -51,7 +64,35 @@ const QUERY_MODE_OPTIONS: RadioButtonOption[] = [
5164
},
5265
];
5366

67+
const TimeFrameIds = {
68+
hour: 'hour',
69+
minute: 'minute',
70+
} as const;
71+
5472
const queryModeSchema = z.nativeEnum(QueryModeIds).catch(QueryModeIds.top);
73+
const timeFrameSchema = z.nativeEnum(TimeFrameIds).catch(TimeFrameIds.hour);
74+
75+
const TIME_FRAME_OPTIONS: SelectOption[] = [
76+
{
77+
value: TimeFrameIds.hour,
78+
content: i18n('timeframe_hour'),
79+
},
80+
{
81+
value: TimeFrameIds.minute,
82+
content: i18n('timeframe_minute'),
83+
},
84+
];
85+
86+
const DEFAULT_TIME_FILTER_VALUE = {
87+
start: {
88+
value: 'now-6h',
89+
type: 'relative',
90+
},
91+
end: {
92+
value: 'now',
93+
type: 'relative',
94+
},
95+
} as const;
5596

5697
interface TopQueriesProps {
5798
tenantName: string;
@@ -62,8 +103,10 @@ export const TopQueries = ({tenantName}: TopQueriesProps) => {
62103
const location = useLocation();
63104
const history = useHistory();
64105
const [_queryMode = QueryModeIds.top, setQueryMode] = useQueryParam('queryMode', StringParam);
106+
const [_timeFrame = TimeFrameIds.hour, setTimeFrame] = useQueryParam('timeFrame', StringParam);
65107

66108
const queryMode = queryModeSchema.parse(_queryMode);
109+
const timeFrame = timeFrameSchema.parse(_timeFrame);
67110

68111
const isTopQueries = queryMode === QueryModeIds.top;
69112

@@ -93,10 +136,30 @@ export const TopQueries = ({tenantName}: TopQueriesProps) => {
93136
dispatch(setTopQueriesFilters({text}));
94137
};
95138

139+
const handleTimeFrameChange = (value: string[]) => {
140+
setTimeFrame(value[0] as TimeFrame);
141+
};
142+
96143
const handleDateRangeChange = (value: DateRangeValues) => {
97144
dispatch(setTopQueriesFilters(value));
98145
};
99146

147+
// Get columns based on query mode
148+
const columns: Column<any>[] = React.useMemo(() => {
149+
return isTopQueries ? getTopQueriesColumns() : getRunningQueriesColumns();
150+
}, [isTopQueries]);
151+
152+
// Use selected columns hook
153+
const {columnsToShow, columnsToSelect, setColumns} = useSelectedColumns(
154+
columns,
155+
isTopQueries
156+
? TOP_QUERIES_SELECTED_COLUMNS_LS_KEY
157+
: RUNNING_QUERIES_SELECTED_COLUMNS_LS_KEY,
158+
TOP_QUERIES_COLUMNS_TITLES,
159+
isTopQueries ? DEFAULT_TOP_QUERIES_COLUMNS : DEFAULT_RUNNING_QUERIES_COLUMNS,
160+
isTopQueries ? REQUIRED_TOP_QUERIES_COLUMNS : REQUIRED_RUNNING_QUERIES_COLUMNS,
161+
);
162+
100163
const DataComponent = isTopQueries ? TopQueriesData : RunningQueriesData;
101164

102165
return (
@@ -107,21 +170,42 @@ export const TopQueries = ({tenantName}: TopQueriesProps) => {
107170
value={queryMode}
108171
onUpdate={setQueryMode}
109172
/>
173+
{isTopQueries && (
174+
<Select
175+
options={TIME_FRAME_OPTIONS}
176+
value={[timeFrame]}
177+
onUpdate={handleTimeFrameChange}
178+
/>
179+
)}
180+
{isTopQueries && (
181+
<DateRange
182+
from={filters.from}
183+
to={filters.to}
184+
onChange={handleDateRangeChange}
185+
defaultValue={DEFAULT_TIME_FILTER_VALUE}
186+
/>
187+
)}
110188
<Search
111189
value={filters.text}
112190
onChange={handleTextSearchUpdate}
113191
placeholder={i18n('filter.text.placeholder')}
114192
className={b('search')}
115193
/>
116-
{isTopQueries ? (
117-
<DateRange
118-
from={filters.from}
119-
to={filters.to}
120-
onChange={handleDateRangeChange}
121-
/>
122-
) : null}
194+
<TableColumnSetup
195+
popupWidth={200}
196+
items={columnsToSelect}
197+
showStatus
198+
onUpdate={setColumns}
199+
sortable={false}
200+
/>
123201
</TableWithControlsLayout.Controls>
124-
<DataComponent database={tenantName} onRowClick={onRowClick} rowClassName={b('row')} />
202+
<DataComponent
203+
database={tenantName}
204+
onRowClick={onRowClick}
205+
rowClassName={b('row')}
206+
timeFrame={timeFrame}
207+
columns={columnsToShow}
208+
/>
125209
</TableWithControlsLayout>
126210
);
127211
};

0 commit comments

Comments
 (0)