Skip to content

Commit b9a8e95

Browse files
committed
feat(TopQueries): date range filter
1 parent e4a3baa commit b9a8e95

File tree

6 files changed

+219
-77
lines changed

6 files changed

+219
-77
lines changed

src/components/DateRange/DateRange.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export const DateRange = ({from, to, className, onChange}: DateRangeProps) => {
5757
<div className={b(null, className)}>
5858
<input
5959
type="datetime-local"
60-
value={startISO}
60+
value={startISO || ''}
6161
max={endISO}
6262
onChange={handleFromChange}
6363
className={b('input')}
@@ -66,7 +66,7 @@ export const DateRange = ({from, to, className, onChange}: DateRangeProps) => {
6666
<input
6767
type="datetime-local"
6868
min={startISO}
69-
value={endISO}
69+
value={endISO || ''}
7070
onChange={handleToChange}
7171
className={b('input')}
7272
/>

src/containers/Tenant/Diagnostics/TopQueries/TopQueries.scss

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,37 @@
11
@import '../../../../styles/mixins.scss';
22

33
.kv-top-queries {
4+
display: flex;
5+
flex-direction: column;
6+
47
height: 100%;
58

69
@include query-data-table;
7-
&__message-container {
8-
padding: 15px 0;
9-
}
1010

1111
&__loader {
1212
display: flex;
1313
justify-content: center;
1414
}
1515

16-
&__owner-container {
17-
margin: 10px;
16+
&__controls {
17+
display: flex;
18+
flex-wrap: wrap;
19+
align-items: baseline;
20+
gap: 16px;
1821

19-
.yc-staff-card {
20-
display: inline-block;
21-
}
22+
margin-bottom: 10px;
2223
}
2324

24-
&__text {
25-
font-size: var(--yc-text-body-1-font-size);
26-
line-height: var(--yc-text-body-1-line-height);
27-
28-
color: var(--yc-color-text-primary);
25+
&__result {
26+
overflow: auto;
27+
flex-grow: 1;
2928

30-
&::first-letter {
31-
color: var(--yc-color-text-danger);
32-
}
33-
}
34-
35-
&__result &__table-content {
36-
& .data-table {
29+
.data-table {
3730
&,
3831
&__table {
3932
width: 100%;
4033
}
34+
4135
&__td {
4236
vertical-align: top;
4337
white-space: pre;

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

Lines changed: 91 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,26 @@
1+
import {useCallback, useEffect, useRef, useState} from 'react';
12
import {useDispatch} from 'react-redux';
23
import cn from 'bem-cn-lite';
34

45
import DataTable, {Column} from '@yandex-cloud/react-data-table';
56
import {Loader} from '@gravity-ui/uikit';
67

8+
import {DateRange, DateRangeValues} from '../../../../components/DateRange';
79
import TruncatedQuery from '../../../../components/TruncatedQuery/TruncatedQuery';
810

911
import {changeUserInput} from '../../../../store/reducers/executeQuery';
10-
import {fetchTopQueries, setTopQueriesState} from '../../../../store/reducers/executeTopQueries';
12+
import {
13+
fetchTopQueries,
14+
setTopQueriesFilters,
15+
setTopQueriesState,
16+
} from '../../../../store/reducers/executeTopQueries';
1117

1218
import type {KeyValueRow} from '../../../../types/api/query';
1319
import type {EPathType} from '../../../../types/api/schema';
20+
import type {ITopQueriesFilters} from '../../../../types/store/executeTopQueries';
21+
import type {IQueryResult} from '../../../../types/store/query';
1422

15-
import {DEFAULT_TABLE_SETTINGS} from '../../../../utils/constants';
23+
import {DEFAULT_TABLE_SETTINGS, HOUR_IN_SECONDS} from '../../../../utils/constants';
1624
import {useAutofetcher, useTypedSelector} from '../../../../utils/hooks';
1725
import {prepareQueryError} from '../../../../utils/query';
1826

@@ -21,7 +29,6 @@ import {TenantGeneralTabsIds} from '../../TenantPages';
2129

2230
import i18n from './i18n';
2331
import './TopQueries.scss';
24-
import {useCallback, useEffect} from 'react';
2532

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

@@ -56,23 +63,70 @@ export const TopQueries = ({path, type, changeSchemaTab}: TopQueriesProps) => {
5663
wasLoaded,
5764
error,
5865
data: {result: data = undefined} = {},
66+
filters: storeFilters,
5967
} = useTypedSelector((state) => state.executeTopQueries);
6068

69+
const preventFetch = useRef(false);
70+
71+
// filters sync between redux state and URL
72+
// component state is for default values,
73+
// default values are determined from the query response, and should not propagate to URL
74+
const [dateRangeFilters, setDateRangeFilters] = useState<ITopQueriesFilters>(storeFilters);
75+
76+
useEffect(() => {
77+
dispatch(setTopQueriesFilters(dateRangeFilters));
78+
}, [dispatch, dateRangeFilters]);
79+
80+
const setDefaultFiltersFromResponse = (responseData?: IQueryResult) => {
81+
const intervalEnd = responseData?.result?.[0]?.IntervalEnd;
82+
83+
if (intervalEnd) {
84+
const to = new Date(intervalEnd).getTime();
85+
const from = new Date(to - HOUR_IN_SECONDS * 1000).getTime();
86+
87+
setDateRangeFilters((currentFilters) => {
88+
// request without filters returns the latest interval with data
89+
// only in this case should update filters in ui
90+
// also don't update if user already interacted with controls
91+
const shouldUpdateFilters = !currentFilters.from && !currentFilters.to;
92+
93+
if (!shouldUpdateFilters) {
94+
return currentFilters;
95+
}
96+
97+
preventFetch.current = true;
98+
99+
return {...currentFilters, from, to};
100+
});
101+
}
102+
};
103+
61104
useAutofetcher(
62-
() => dispatch(fetchTopQueries({database: path})),
63-
[dispatch, path],
105+
(isBackground) => {
106+
if (preventFetch.current) {
107+
preventFetch.current = false;
108+
return;
109+
}
110+
111+
if (!isBackground) {
112+
dispatch(
113+
setTopQueriesState({
114+
wasLoaded: false,
115+
data: undefined,
116+
}),
117+
);
118+
}
119+
120+
// @ts-expect-error
121+
// typed dispatch required, remove error expectation after adding it
122+
dispatch(fetchTopQueries({database: path, filters: dateRangeFilters})).then(
123+
setDefaultFiltersFromResponse,
124+
);
125+
},
126+
[dispatch, dateRangeFilters, path],
64127
autorefresh,
65128
);
66129

67-
useEffect(() => {
68-
dispatch(
69-
setTopQueriesState({
70-
wasLoaded: false,
71-
data: undefined,
72-
}),
73-
);
74-
}, [dispatch, path]);
75-
76130
const handleRowClick = useCallback(
77131
(row) => {
78132
const {QueryText: input} = row;
@@ -83,6 +137,10 @@ export const TopQueries = ({path, type, changeSchemaTab}: TopQueriesProps) => {
83137
[changeSchemaTab, dispatch],
84138
);
85139

140+
const handleDateRangeChange = (value: DateRangeValues) => {
141+
setDateRangeFilters((currentFilters) => ({...currentFilters, ...value}));
142+
};
143+
86144
const renderLoader = () => {
87145
return (
88146
<div className={b('loader')}>
@@ -106,20 +164,27 @@ export const TopQueries = ({path, type, changeSchemaTab}: TopQueriesProps) => {
106164

107165
return (
108166
<div className={b('result')}>
109-
<div className={b('table-wrapper')}>
110-
<div className={b('table-content')}>
111-
<DataTable
112-
columns={COLUMNS}
113-
data={data}
114-
settings={DEFAULT_TABLE_SETTINGS}
115-
onRowClick={handleRowClick}
116-
theme="yandex-cloud"
117-
/>
118-
</div>
119-
</div>
167+
<DataTable
168+
columns={COLUMNS}
169+
data={data}
170+
settings={DEFAULT_TABLE_SETTINGS}
171+
onRowClick={handleRowClick}
172+
theme="yandex-cloud"
173+
/>
120174
</div>
121175
);
122176
};
123177

124-
return <div className={b()}>{renderContent()}</div>;
178+
return (
179+
<div className={b()}>
180+
<div className={b('controls')}>
181+
<DateRange
182+
from={dateRangeFilters.from}
183+
to={dateRangeFilters.to}
184+
onChange={handleDateRangeChange}
185+
/>
186+
</div>
187+
{renderContent()}
188+
</div>
189+
);
125190
};

0 commit comments

Comments
 (0)