Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion src/containers/Tenant/Diagnostics/DiagnosticsPages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const schema = {

const topQueries = {
id: TENANT_DIAGNOSTICS_TABS_IDS.topQueries,
title: 'Top queries',
title: 'Queries',
};

const topShards = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import type {Column} from '@gravity-ui/react-data-table';
import DataTable from '@gravity-ui/react-data-table';

import {ResponseError} from '../../../../components/Errors/ResponseError';
import {ResizeableDataTable} from '../../../../components/ResizeableDataTable/ResizeableDataTable';
import {TableWithControlsLayout} from '../../../../components/TableWithControlsLayout/TableWithControlsLayout';
import {TruncatedQuery} from '../../../../components/TruncatedQuery/TruncatedQuery';
import {topQueriesApi} from '../../../../store/reducers/executeTopQueries/executeTopQueries';
import type {KeyValueRow} from '../../../../types/api/query';
import {cn} from '../../../../utils/cn';
import {formatDateTime} from '../../../../utils/dataFormatters/dataFormatters';
import {useAutoRefreshInterval, useTypedSelector} from '../../../../utils/hooks';
import {MAX_QUERY_HEIGHT, QUERY_TABLE_SETTINGS} from '../../utils/constants';

import i18n from './i18n';

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

interface Props {
database: string;
}

const RUNNING_QUERIES_COLUMNS_WIDTH_LS_KEY = 'runningQueriesColumnsWidth';

const columns: Column<KeyValueRow>[] = [
{
name: 'UserSID',
header: i18n('col_user'),
render: ({row}) => <div className={b('user-sid')}>{row.UserSID || '–'}</div>,
sortable: true,
},
{
name: 'QueryStartAt',
header: i18n('col_start-time'),
render: ({row}) => formatDateTime(new Date(row.QueryStartAt as string).getTime()),
sortable: true,
resizeable: false,
defaultOrder: DataTable.DESCENDING,
},
{
name: 'Query',
header: i18n('col_query-text'),
render: ({row}) => (
<div className={b('query')}>
<TruncatedQuery value={row.Query?.toString()} maxQueryHeight={MAX_QUERY_HEIGHT} />
</div>
),
width: 500,
sortable: false,
},
{
name: 'ApplicationName',
header: i18n('col_app'),
render: ({row}) => <div className={b('user-sid')}>{row.ApplicationName || '–'}</div>,
sortable: true,
},
];

export const RunningQueriesData = ({database}: Props) => {
const [autoRefreshInterval] = useAutoRefreshInterval();
const filters = useTypedSelector((state) => state.executeTopQueries);
const {
currentData: data,
isFetching,
error,
} = topQueriesApi.useGetRunningQueriesQuery(
{
database,
filters,
},
{pollingInterval: autoRefreshInterval},
);

return (
<TableWithControlsLayout.Table loading={isFetching && data === undefined}>
{error ? (
<ResponseError error={error} />
) : (
<ResizeableDataTable
emptyDataMessage={i18n('no-data')}
columnsWidthLSKey={RUNNING_QUERIES_COLUMNS_WIDTH_LS_KEY}
columns={columns}
data={data || []}
settings={QUERY_TABLE_SETTINGS}
/>
)}
</TableWithControlsLayout.Table>
);
};
105 changes: 37 additions & 68 deletions src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,40 @@
import React from 'react';

import type {RadioButtonOption} from '@gravity-ui/uikit';
import {RadioButton} from '@gravity-ui/uikit';
import {useHistory, useLocation} from 'react-router-dom';
import {StringParam, useQueryParam} from 'use-query-params';

import type {DateRangeValues} from '../../../../components/DateRange';
import {DateRange} from '../../../../components/DateRange';
import {ResponseError} from '../../../../components/Errors/ResponseError';
import {ResizeableDataTable} from '../../../../components/ResizeableDataTable/ResizeableDataTable';
import {Search} from '../../../../components/Search';
import {TableWithControlsLayout} from '../../../../components/TableWithControlsLayout/TableWithControlsLayout';
import {parseQuery} from '../../../../routes';
import {changeUserInput} from '../../../../store/reducers/executeQuery';
import {
setTopQueriesFilters,
topQueriesApi,
} from '../../../../store/reducers/executeTopQueries/executeTopQueries';
import {setTopQueriesFilters} from '../../../../store/reducers/executeTopQueries/executeTopQueries';
import {
TENANT_PAGE,
TENANT_PAGES_IDS,
TENANT_QUERY_TABS_ID,
} from '../../../../store/reducers/tenant/constants';
import type {EPathType} from '../../../../types/api/schema';
import {cn} from '../../../../utils/cn';
import {isSortableTopQueriesProperty} from '../../../../utils/diagnostics';
import {useAutoRefreshInterval, useTypedDispatch, useTypedSelector} from '../../../../utils/hooks';
import {parseQueryErrorToString} from '../../../../utils/query';
import {useTypedDispatch, useTypedSelector} from '../../../../utils/hooks';
import {TenantTabsGroups, getTenantPath} from '../../TenantPages';
import {QUERY_TABLE_SETTINGS} from '../../utils/constants';
import {isColumnEntityType} from '../../utils/schema';

import {TOP_QUERIES_COLUMNS, TOP_QUERIES_COLUMNS_WIDTH_LS_KEY} from './getTopQueriesColumns';
import {RunningQueriesData} from './RunningQueriesData';
import {TopQueriesData} from './TopQueriesData';
import i18n from './i18n';

import './TopQueries.scss';

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

const QUERY_MODE_OPTIONS: RadioButtonOption[] = [
{value: 'top', content: i18n('mode_top')},
{value: 'running', content: i18n('mode_running')},
];

interface TopQueriesProps {
tenantName: string;
type?: EPathType;
Expand All @@ -44,30 +44,14 @@ export const TopQueries = ({tenantName, type}: TopQueriesProps) => {
const dispatch = useTypedDispatch();
const location = useLocation();
const history = useHistory();
const [queryMode = 'top', setQueryMode] = useQueryParam('queryMode', StringParam);

const [autoRefreshInterval] = useAutoRefreshInterval();
const isTopQueries = queryMode === 'top';

const filters = useTypedSelector((state) => state.executeTopQueries);
const {currentData, isFetching, error} = topQueriesApi.useGetTopQueriesQuery(
{
database: tenantName,
filters,
},
{pollingInterval: autoRefreshInterval},
);
const loading = isFetching && currentData === undefined;
const {result: data} = currentData || {};

const rawColumns = TOP_QUERIES_COLUMNS;
const columns = rawColumns.map((column) => ({
...column,
sortable: isSortableTopQueriesProperty(column.name),
}));

const handleRowClick = React.useCallback(
(row: any) => {
const {QueryText: input} = row;

const onRowClick = React.useCallback(
(input: string) => {
dispatch(changeUserInput({input}));

const queryParams = parseQuery(location);
Expand All @@ -91,48 +75,33 @@ export const TopQueries = ({tenantName, type}: TopQueriesProps) => {
dispatch(setTopQueriesFilters(value));
};

const renderContent = () => {
if (error && !data) {
return null;
}

if (!data || isColumnEntityType(type)) {
return i18n('no-data');
}

return (
<ResizeableDataTable
columnsWidthLSKey={TOP_QUERIES_COLUMNS_WIDTH_LS_KEY}
columns={columns}
data={data}
settings={QUERY_TABLE_SETTINGS}
onRowClick={handleRowClick}
rowClassName={() => b('row')}
/>
);
};

const renderControls = () => {
return (
<React.Fragment>
return (
<TableWithControlsLayout>
<TableWithControlsLayout.Controls>
<RadioButton
options={QUERY_MODE_OPTIONS}
value={queryMode}
onUpdate={setQueryMode}
/>
<Search
value={filters.text}
onChange={handleTextSearchUpdate}
placeholder={i18n('filter.text.placeholder')}
className={b('search')}
/>
<DateRange from={filters.from} to={filters.to} onChange={handleDateRangeChange} />
</React.Fragment>
);
};

return (
<TableWithControlsLayout>
<TableWithControlsLayout.Controls>{renderControls()}</TableWithControlsLayout.Controls>
{error ? <ResponseError error={parseQueryErrorToString(error)} /> : null}
<TableWithControlsLayout.Table loading={loading}>
{renderContent()}
</TableWithControlsLayout.Table>
{isTopQueries ? (
<DateRange
from={filters.from}
to={filters.to}
onChange={handleDateRangeChange}
/>
) : null}
</TableWithControlsLayout.Controls>
{isTopQueries ? (
<TopQueriesData database={tenantName} type={type} onRowClick={onRowClick} />
) : (
<RunningQueriesData database={tenantName} />
)}
</TableWithControlsLayout>
);
};
71 changes: 71 additions & 0 deletions src/containers/Tenant/Diagnostics/TopQueries/TopQueriesData.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import {ResponseError} from '../../../../components/Errors/ResponseError';
import {ResizeableDataTable} from '../../../../components/ResizeableDataTable/ResizeableDataTable';
import {TableWithControlsLayout} from '../../../../components/TableWithControlsLayout/TableWithControlsLayout';
import {topQueriesApi} from '../../../../store/reducers/executeTopQueries/executeTopQueries';
import type {KeyValueRow} from '../../../../types/api/query';
import type {EPathType} from '../../../../types/api/schema';
import {cn} from '../../../../utils/cn';
import {isSortableTopQueriesProperty} from '../../../../utils/diagnostics';
import {useAutoRefreshInterval, useTypedSelector} from '../../../../utils/hooks';
import {QUERY_TABLE_SETTINGS} from '../../utils/constants';
import {isColumnEntityType} from '../../utils/schema';

import {TOP_QUERIES_COLUMNS, TOP_QUERIES_COLUMNS_WIDTH_LS_KEY} from './getTopQueriesColumns';
import i18n from './i18n';

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

interface Props {
database: string;
onRowClick: (row: any) => void;
type?: EPathType;
}

export const TopQueriesData = ({database, onRowClick, type}: Props) => {
const [autoRefreshInterval] = useAutoRefreshInterval();
const filters = useTypedSelector((state) => state.executeTopQueries);
const {currentData, isFetching, error} = topQueriesApi.useGetTopQueriesQuery(
{
database,
filters,
},
{pollingInterval: autoRefreshInterval},
);
const loading = isFetching && currentData === undefined;
const {result: data} = currentData || {};

const rawColumns = TOP_QUERIES_COLUMNS;
const columns = rawColumns.map((column) => ({
...column,
sortable: isSortableTopQueriesProperty(column.name),
}));

const handleRowClick = (row: KeyValueRow) => {
return onRowClick(row.QueryText as string);
};

if (error && !data) {
return (
<TableWithControlsLayout.Table>
<ResponseError error={error} />
</TableWithControlsLayout.Table>
);
}

if (!data || isColumnEntityType(type)) {
return <TableWithControlsLayout.Table>{i18n('no-data')}</TableWithControlsLayout.Table>;
}

return (
<TableWithControlsLayout.Table loading={loading}>
<ResizeableDataTable
columnsWidthLSKey={TOP_QUERIES_COLUMNS_WIDTH_LS_KEY}
columns={columns}
data={data}
settings={QUERY_TABLE_SETTINGS}
onRowClick={handleRowClick}
rowClassName={() => b('row')}
/>
</TableWithControlsLayout.Table>
);
};
8 changes: 7 additions & 1 deletion src/containers/Tenant/Diagnostics/TopQueries/i18n/en.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
{
"no-data": "No data",
"filter.text.placeholder": "Search by query text..."
"filter.text.placeholder": "Search by query text...",
"mode_top": "Top",
"mode_running": "Running",
"col_user": "User",
"col_start-time": "Start time",
"col_query-text": "Query text",
"col_app": "Application"
}
8 changes: 7 additions & 1 deletion src/containers/Tenant/Diagnostics/TopQueries/i18n/ru.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
{
"no-data": "Нет данных",
"filter.text.placeholder": "Искать по тексту запроса..."
"filter.text.placeholder": "Искать по тексту запроса...",
"mode_top": "Top",
"mode_running": "Running",
"col_user": "User",
"col_start-time": "Start time",
"col_query-text": "Query text",
"col_app": "Application"
}
Loading
Loading