Skip to content

Commit acd4a1e

Browse files
authored
feat: show running queries (#1313)
1 parent 56839bc commit acd4a1e

File tree

10 files changed

+244
-81
lines changed

10 files changed

+244
-81
lines changed

src/containers/Tenant/Diagnostics/Diagnostics.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ function Diagnostics(props: DiagnosticsProps) {
9595
return <SchemaViewer path={path} tenantName={tenantName} type={type} extended />;
9696
}
9797
case TENANT_DIAGNOSTICS_TABS_IDS.topQueries: {
98-
return <TopQueries tenantName={tenantName} type={type} />;
98+
return <TopQueries tenantName={tenantName} />;
9999
}
100100
case TENANT_DIAGNOSTICS_TABS_IDS.topShards: {
101101
return <TopShards tenantName={tenantName} path={path} type={type} />;

src/containers/Tenant/Diagnostics/DiagnosticsPages.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const schema = {
1919

2020
const topQueries = {
2121
id: TENANT_DIAGNOSTICS_TABS_IDS.topQueries,
22-
title: 'Top queries',
22+
title: 'Queries',
2323
};
2424

2525
const topShards = {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React from 'react';
2+
3+
import {ResponseError} from '../../../../components/Errors/ResponseError';
4+
import {ResizeableDataTable} from '../../../../components/ResizeableDataTable/ResizeableDataTable';
5+
import {TableWithControlsLayout} from '../../../../components/TableWithControlsLayout/TableWithControlsLayout';
6+
import {topQueriesApi} from '../../../../store/reducers/executeTopQueries/executeTopQueries';
7+
import {useAutoRefreshInterval, useTypedSelector} from '../../../../utils/hooks';
8+
import {parseQueryErrorToString} from '../../../../utils/query';
9+
import {QUERY_TABLE_SETTINGS} from '../../utils/constants';
10+
11+
import {
12+
RUNNING_QUERIES_COLUMNS,
13+
RUNNING_QUERIES_COLUMNS_WIDTH_LS_KEY,
14+
} from './getTopQueriesColumns';
15+
import i18n from './i18n';
16+
17+
interface Props {
18+
database: string;
19+
}
20+
21+
export const RunningQueriesData = ({database}: Props) => {
22+
const [autoRefreshInterval] = useAutoRefreshInterval();
23+
const filters = useTypedSelector((state) => state.executeTopQueries);
24+
const {
25+
currentData: data,
26+
isFetching,
27+
error,
28+
} = topQueriesApi.useGetRunningQueriesQuery(
29+
{
30+
database,
31+
filters,
32+
},
33+
{pollingInterval: autoRefreshInterval},
34+
);
35+
36+
return (
37+
<React.Fragment>
38+
{error ? <ResponseError error={parseQueryErrorToString(error)} /> : null}
39+
<TableWithControlsLayout.Table loading={isFetching && data === undefined}>
40+
<ResizeableDataTable
41+
emptyDataMessage={i18n('no-data')}
42+
columnsWidthLSKey={RUNNING_QUERIES_COLUMNS_WIDTH_LS_KEY}
43+
columns={RUNNING_QUERIES_COLUMNS}
44+
data={data || []}
45+
settings={QUERY_TABLE_SETTINGS}
46+
/>
47+
</TableWithControlsLayout.Table>
48+
</React.Fragment>
49+
);
50+
};
Lines changed: 58 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,75 @@
11
import React from 'react';
22

3+
import type {RadioButtonOption} from '@gravity-ui/uikit';
4+
import {RadioButton} from '@gravity-ui/uikit';
35
import {useHistory, useLocation} from 'react-router-dom';
6+
import {StringParam, useQueryParam} from 'use-query-params';
7+
import {z} from 'zod';
48

59
import type {DateRangeValues} from '../../../../components/DateRange';
610
import {DateRange} from '../../../../components/DateRange';
7-
import {ResponseError} from '../../../../components/Errors/ResponseError';
8-
import {ResizeableDataTable} from '../../../../components/ResizeableDataTable/ResizeableDataTable';
911
import {Search} from '../../../../components/Search';
1012
import {TableWithControlsLayout} from '../../../../components/TableWithControlsLayout/TableWithControlsLayout';
1113
import {parseQuery} from '../../../../routes';
1214
import {changeUserInput} from '../../../../store/reducers/executeQuery';
13-
import {
14-
setTopQueriesFilters,
15-
topQueriesApi,
16-
} from '../../../../store/reducers/executeTopQueries/executeTopQueries';
15+
import {setTopQueriesFilters} from '../../../../store/reducers/executeTopQueries/executeTopQueries';
1716
import {
1817
TENANT_PAGE,
1918
TENANT_PAGES_IDS,
2019
TENANT_QUERY_TABS_ID,
2120
} from '../../../../store/reducers/tenant/constants';
22-
import type {EPathType} from '../../../../types/api/schema';
2321
import {cn} from '../../../../utils/cn';
24-
import {isSortableTopQueriesProperty} from '../../../../utils/diagnostics';
25-
import {useAutoRefreshInterval, useTypedDispatch, useTypedSelector} from '../../../../utils/hooks';
26-
import {parseQueryErrorToString} from '../../../../utils/query';
22+
import {useTypedDispatch, useTypedSelector} from '../../../../utils/hooks';
2723
import {TenantTabsGroups, getTenantPath} from '../../TenantPages';
28-
import {QUERY_TABLE_SETTINGS} from '../../utils/constants';
29-
import {isColumnEntityType} from '../../utils/schema';
3024

31-
import {TOP_QUERIES_COLUMNS, TOP_QUERIES_COLUMNS_WIDTH_LS_KEY} from './getTopQueriesColumns';
25+
import {RunningQueriesData} from './RunningQueriesData';
26+
import {TopQueriesData} from './TopQueriesData';
3227
import i18n from './i18n';
3328

3429
import './TopQueries.scss';
3530

3631
const b = cn('kv-top-queries');
3732

33+
const QueryModeIds = {
34+
top: 'top',
35+
running: 'running',
36+
} as const;
37+
38+
const QUERY_MODE_OPTIONS: RadioButtonOption[] = [
39+
{
40+
value: QueryModeIds.top,
41+
get content() {
42+
return i18n('mode_top');
43+
},
44+
},
45+
{
46+
value: QueryModeIds.running,
47+
get content() {
48+
return i18n('mode_running');
49+
},
50+
},
51+
];
52+
53+
const queryModeSchema = z.nativeEnum(QueryModeIds).catch(QueryModeIds.top);
54+
3855
interface TopQueriesProps {
3956
tenantName: string;
40-
type?: EPathType;
4157
}
4258

43-
export const TopQueries = ({tenantName, type}: TopQueriesProps) => {
59+
export const TopQueries = ({tenantName}: TopQueriesProps) => {
4460
const dispatch = useTypedDispatch();
4561
const location = useLocation();
4662
const history = useHistory();
63+
const [_queryMode = QueryModeIds.top, setQueryMode] = useQueryParam('queryMode', StringParam);
4764

48-
const [autoRefreshInterval] = useAutoRefreshInterval();
49-
50-
const filters = useTypedSelector((state) => state.executeTopQueries);
51-
const {currentData, isFetching, error} = topQueriesApi.useGetTopQueriesQuery(
52-
{
53-
database: tenantName,
54-
filters,
55-
},
56-
{pollingInterval: autoRefreshInterval},
57-
);
58-
const loading = isFetching && currentData === undefined;
59-
const {result: data} = currentData || {};
65+
const queryMode = queryModeSchema.parse(_queryMode);
6066

61-
const rawColumns = TOP_QUERIES_COLUMNS;
62-
const columns = rawColumns.map((column) => ({
63-
...column,
64-
sortable: isSortableTopQueriesProperty(column.name),
65-
}));
67+
const isTopQueries = queryMode === QueryModeIds.top;
6668

67-
const handleRowClick = React.useCallback(
68-
(row: any) => {
69-
const {QueryText: input} = row;
69+
const filters = useTypedSelector((state) => state.executeTopQueries);
7070

71+
const onRowClick = React.useCallback(
72+
(input: string) => {
7173
dispatch(changeUserInput({input}));
7274

7375
const queryParams = parseQuery(location);
@@ -91,48 +93,33 @@ export const TopQueries = ({tenantName, type}: TopQueriesProps) => {
9193
dispatch(setTopQueriesFilters(value));
9294
};
9395

94-
const renderContent = () => {
95-
if (error && !data) {
96-
return null;
97-
}
98-
99-
if (!data || isColumnEntityType(type)) {
100-
return i18n('no-data');
101-
}
102-
103-
return (
104-
<ResizeableDataTable
105-
columnsWidthLSKey={TOP_QUERIES_COLUMNS_WIDTH_LS_KEY}
106-
columns={columns}
107-
data={data}
108-
settings={QUERY_TABLE_SETTINGS}
109-
onRowClick={handleRowClick}
110-
rowClassName={() => b('row')}
111-
/>
112-
);
113-
};
114-
115-
const renderControls = () => {
116-
return (
117-
<React.Fragment>
96+
return (
97+
<TableWithControlsLayout>
98+
<TableWithControlsLayout.Controls>
99+
<RadioButton
100+
options={QUERY_MODE_OPTIONS}
101+
value={queryMode}
102+
onUpdate={setQueryMode}
103+
/>
118104
<Search
119105
value={filters.text}
120106
onChange={handleTextSearchUpdate}
121107
placeholder={i18n('filter.text.placeholder')}
122108
className={b('search')}
123109
/>
124-
<DateRange from={filters.from} to={filters.to} onChange={handleDateRangeChange} />
125-
</React.Fragment>
126-
);
127-
};
128-
129-
return (
130-
<TableWithControlsLayout>
131-
<TableWithControlsLayout.Controls>{renderControls()}</TableWithControlsLayout.Controls>
132-
{error ? <ResponseError error={parseQueryErrorToString(error)} /> : null}
133-
<TableWithControlsLayout.Table loading={loading}>
134-
{renderContent()}
135-
</TableWithControlsLayout.Table>
110+
{isTopQueries ? (
111+
<DateRange
112+
from={filters.from}
113+
to={filters.to}
114+
onChange={handleDateRangeChange}
115+
/>
116+
) : null}
117+
</TableWithControlsLayout.Controls>
118+
{isTopQueries ? (
119+
<TopQueriesData database={tenantName} onRowClick={onRowClick} />
120+
) : (
121+
<RunningQueriesData database={tenantName} />
122+
)}
136123
</TableWithControlsLayout>
137124
);
138125
};
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import React from 'react';
2+
3+
import {ResponseError} from '../../../../components/Errors/ResponseError';
4+
import {ResizeableDataTable} from '../../../../components/ResizeableDataTable/ResizeableDataTable';
5+
import {TableWithControlsLayout} from '../../../../components/TableWithControlsLayout/TableWithControlsLayout';
6+
import {topQueriesApi} from '../../../../store/reducers/executeTopQueries/executeTopQueries';
7+
import type {KeyValueRow} from '../../../../types/api/query';
8+
import {cn} from '../../../../utils/cn';
9+
import {isSortableTopQueriesProperty} from '../../../../utils/diagnostics';
10+
import {useAutoRefreshInterval, useTypedSelector} from '../../../../utils/hooks';
11+
import {parseQueryErrorToString} from '../../../../utils/query';
12+
import {QUERY_TABLE_SETTINGS} from '../../utils/constants';
13+
14+
import {TOP_QUERIES_COLUMNS, TOP_QUERIES_COLUMNS_WIDTH_LS_KEY} from './getTopQueriesColumns';
15+
import i18n from './i18n';
16+
17+
const b = cn('kv-top-queries');
18+
19+
interface Props {
20+
database: string;
21+
onRowClick: (query: string) => void;
22+
}
23+
24+
export const TopQueriesData = ({database, onRowClick}: Props) => {
25+
const [autoRefreshInterval] = useAutoRefreshInterval();
26+
const filters = useTypedSelector((state) => state.executeTopQueries);
27+
const {currentData, isFetching, error} = topQueriesApi.useGetTopQueriesQuery(
28+
{
29+
database,
30+
filters,
31+
},
32+
{pollingInterval: autoRefreshInterval},
33+
);
34+
const {result: data} = currentData || {};
35+
36+
const rawColumns = TOP_QUERIES_COLUMNS;
37+
const columns = rawColumns.map((column) => ({
38+
...column,
39+
sortable: isSortableTopQueriesProperty(column.name),
40+
}));
41+
42+
const handleRowClick = (row: KeyValueRow) => {
43+
return onRowClick(row.QueryText as string);
44+
};
45+
46+
return (
47+
<React.Fragment>
48+
{error ? <ResponseError error={parseQueryErrorToString(error)} /> : null}
49+
<TableWithControlsLayout.Table loading={isFetching && currentData === undefined}>
50+
<ResizeableDataTable
51+
emptyDataMessage={i18n('no-data')}
52+
columnsWidthLSKey={TOP_QUERIES_COLUMNS_WIDTH_LS_KEY}
53+
columns={columns}
54+
data={data || []}
55+
settings={QUERY_TABLE_SETTINGS}
56+
onRowClick={handleRowClick}
57+
rowClassName={() => b('row')}
58+
/>
59+
</TableWithControlsLayout.Table>
60+
</React.Fragment>
61+
);
62+
};

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,14 @@ import {generateHash} from '../../../../utils/generateHash';
1313
import {formatToMs, parseUsToMs} from '../../../../utils/timeParsers';
1414
import {MAX_QUERY_HEIGHT} from '../../utils/constants';
1515

16+
import i18n from './i18n';
17+
1618
import './TopQueries.scss';
1719

1820
const b = cn('kv-top-queries');
1921

2022
export const TOP_QUERIES_COLUMNS_WIDTH_LS_KEY = 'topQueriesColumnsWidth';
23+
export const RUNNING_QUERIES_COLUMNS_WIDTH_LS_KEY = 'runningQueriesColumnsWidth';
2124

2225
const cpuTimeUsColumn: Column<KeyValueRow> = {
2326
name: TOP_QUERIES_COLUMNS_IDS.CPUTimeUs,
@@ -94,6 +97,26 @@ const durationColumn: Column<KeyValueRow> = {
9497
width: 150,
9598
};
9699

100+
const queryStartColumn: Column<KeyValueRow> = {
101+
name: 'QueryStartAt',
102+
get header() {
103+
return i18n('col_start-time');
104+
},
105+
render: ({row}) => formatDateTime(new Date(row.QueryStartAt as string).getTime()),
106+
sortable: true,
107+
resizeable: false,
108+
defaultOrder: DataTable.DESCENDING,
109+
};
110+
111+
const applicationColumn: Column<KeyValueRow> = {
112+
name: 'ApplicationName',
113+
get header() {
114+
return i18n('col_app');
115+
},
116+
render: ({row}) => <div className={b('user-sid')}>{row.ApplicationName || '–'}</div>,
117+
sortable: true,
118+
};
119+
97120
export const TOP_QUERIES_COLUMNS = [
98121
cpuTimeUsColumn,
99122
queryTextColumn,
@@ -109,3 +132,10 @@ export const TENANT_OVERVIEW_TOP_QUERUES_COLUMNS = [
109132
oneLineQueryTextColumn,
110133
cpuTimeUsColumn,
111134
];
135+
136+
export const RUNNING_QUERIES_COLUMNS = [
137+
userSIDColumn,
138+
queryStartColumn,
139+
queryTextColumn,
140+
applicationColumn,
141+
];
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
{
22
"no-data": "No data",
3-
"filter.text.placeholder": "Search by query text..."
3+
"filter.text.placeholder": "Search by query text...",
4+
"mode_top": "Top",
5+
"mode_running": "Running",
6+
"col_user": "User",
7+
"col_start-time": "Start time",
8+
"col_query-text": "Query text",
9+
"col_app": "Application"
410
}
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import {registerKeysets} from '../../../../../utils/i18n';
22

33
import en from './en.json';
4-
import ru from './ru.json';
54

65
const COMPONENT = 'ydb-diagnostics-top-queries';
76

8-
export default registerKeysets(COMPONENT, {ru, en});
7+
export default registerKeysets(COMPONENT, {en});

src/containers/Tenant/Diagnostics/TopQueries/i18n/ru.json

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

0 commit comments

Comments
 (0)