1+ import { useCallback , useEffect , useRef , useState } from 'react' ;
12import { useDispatch } from 'react-redux' ;
23import cn from 'bem-cn-lite' ;
34
45import DataTable , { Column } from '@yandex-cloud/react-data-table' ;
56import { Loader } from '@gravity-ui/uikit' ;
67
8+ import { DateRange , DateRangeValues } from '../../../../components/DateRange' ;
79import TruncatedQuery from '../../../../components/TruncatedQuery/TruncatedQuery' ;
810
911import { 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
1218import type { KeyValueRow } from '../../../../types/api/query' ;
1319import 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' ;
1624import { useAutofetcher , useTypedSelector } from '../../../../utils/hooks' ;
1725import { prepareQueryError } from '../../../../utils/query' ;
1826
@@ -21,7 +29,6 @@ import {TenantGeneralTabsIds} from '../../TenantPages';
2129
2230import i18n from './i18n' ;
2331import './TopQueries.scss' ;
24- import { useCallback , useEffect } from 'react' ;
2532
2633const 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