2022-08-25 21:18:13 +00:00
import React , { useState , useCallback , useRef , useEffect } from "react" ;
2021-04-14 16:52:15 +00:00
import classnames from "classnames" ;
2024-04-15 13:17:08 +00:00
import { Row } from "react-table" ;
2022-04-22 16:45:35 +00:00
import ReactTooltip from "react-tooltip" ;
import useDeepEffect from "hooks/useDeepEffect" ;
2024-10-09 15:09:38 +00:00
import { noop } from "lodash" ;
2021-09-10 19:06:37 +00:00
2021-12-29 18:10:54 +00:00
import SearchField from "components/forms/fields/SearchField" ;
2021-04-14 16:52:15 +00:00
import Pagination from "components/Pagination" ;
2021-09-10 19:06:37 +00:00
import Button from "components/buttons/Button" ;
2023-05-30 21:16:47 +00:00
import Icon from "components/Icon/Icon" ;
2025-02-22 00:56:20 +00:00
import GitOpsModeTooltipWrapper from "components/GitOpsModeTooltipWrapper" ;
2023-12-09 00:54:24 +00:00
import { COLORS } from "styles/var/colors" ;
2021-04-04 12:45:24 +00:00
2021-04-14 16:52:15 +00:00
import DataTable from "./DataTable/DataTable" ;
2023-04-25 12:44:08 +00:00
import { IActionButtonProps } from "./DataTable/ActionButton/ActionButton" ;
2021-04-04 12:45:24 +00:00
2022-01-31 22:24:20 +00:00
export interface ITableQueryData {
pageIndex : number ;
pageSize : number ;
2021-04-04 12:45:24 +00:00
searchQuery : string ;
sortHeader : string ;
sortDirection : string ;
}
2022-11-22 16:13:33 +00:00
interface IRowProps extends Row {
original : {
id? : number ;
2024-01-31 18:32:45 +00:00
os_version_id? : string ; // Required for onSelectSingleRow of SoftwareOSTable.tsx
2024-02-12 14:02:00 +00:00
cve? : string ; // Required for onSelectSingleRow of SoftwareVulnerabilityTable.tsx
2022-11-22 16:13:33 +00:00
} ;
}
2021-04-04 12:45:24 +00:00
2025-02-22 00:56:20 +00:00
interface ITableContainerActionButtonProps extends IActionButtonProps {
gitOpsModeCompatible? : boolean ;
}
2024-03-04 13:14:50 +00:00
interface ITableContainerProps < T = any > {
2023-12-09 00:54:24 +00:00
columnConfigs : any ; // TODO: Figure out type
2021-07-10 17:29:27 +00:00
data : any ; // TODO: Figure out type
2021-04-04 12:45:24 +00:00
isLoading : boolean ;
2021-08-03 19:42:48 +00:00
manualSortBy? : boolean ;
2021-09-10 19:06:37 +00:00
defaultSortHeader? : string ;
defaultSortDirection? : string ;
2023-01-04 16:41:15 +00:00
defaultSearchQuery? : string ;
2025-03-31 15:44:04 +00:00
/** When page index is externally managed like from the URL, this prop must be set to control currentPageIndex */
pageIndex? : number ;
2024-10-25 10:27:44 +00:00
defaultSelectedRows? : Record < string , boolean > ;
2023-05-11 19:25:56 +00:00
/** Button visible above the table container next to search bar */
2025-02-22 00:56:20 +00:00
actionButton? : ITableContainerActionButtonProps ;
2021-08-30 23:02:53 +00:00
inputPlaceHolder? : string ;
2021-04-09 10:44:57 +00:00
disableActionButton? : boolean ;
2021-09-10 19:06:37 +00:00
disableMultiRowSelect? : boolean ;
2024-06-14 17:12:56 +00:00
/** resultsTitle used in DataTable for matching results text */
resultsTitle? : string ;
2021-04-04 12:45:24 +00:00
additionalQueries? : string ;
emptyComponent : React.ElementType ;
className? : string ;
2021-07-10 17:29:27 +00:00
showMarkAllPages : boolean ;
isAllPagesSelected : boolean ; // TODO: make dependent on showMarkAllPages
toggleAllPagesSelected? : any ; // TODO: an event type and make it dependent on showMarkAllPages
2021-07-15 17:01:52 +00:00
searchable? : boolean ;
wideSearch? : boolean ;
disablePagination? : boolean ;
2025-04-02 13:10:08 +00:00
/ * *
* Disables the "Next" button when the last page contains exactly the page size items .
* This is determined using either the API ' s ` meta.has_next_page ` response
* or by calculating ` isLastPage ` in the frontend .
* /
disableNextPage? : boolean ;
2021-07-19 18:02:41 +00:00
disableCount? : boolean ;
2023-05-11 19:25:56 +00:00
/** Main button after selecting a row */
primarySelectAction? : IActionButtonProps ;
/** Secondary button/s after selecting a row */
secondarySelectActions? : IActionButtonProps [ ] ; // TODO: Combine with primarySelectAction as these are all rendered in the same spot
2021-10-14 15:11:53 +00:00
searchToolTipText? : string ;
2024-05-23 20:30:24 +00:00
// TODO - consolidate this functionality within `filters`
2021-11-09 17:31:28 +00:00
searchQueryColumn? : string ;
2024-05-23 20:30:24 +00:00
// TODO - consolidate this functionality within `filters`
2021-11-09 17:31:28 +00:00
selectedDropdownFilter? : string ;
2021-11-05 04:16:42 +00:00
isClientSidePagination? : boolean ;
2023-05-11 19:25:56 +00:00
/** Used to set URL to correct path and include page query param */
onClientSidePaginationChange ? : ( pageIndex : number ) = > void ;
2024-10-25 10:27:44 +00:00
/** Sets the table to filter the data on the client */
2021-11-09 17:31:28 +00:00
isClientSideFilter? : boolean ;
2023-05-11 19:25:56 +00:00
/ * * i s M u l t i C o l u m n F i l t e r i s u s e d t o p r e s e r v e t h e t a b l e h e a d e r s
in lieu of displaying the empty component when client - side filtering yields zero results * /
isMultiColumnFilter? : boolean ;
2022-11-02 18:18:08 +00:00
disableHighlightOnHover? : boolean ;
2021-11-15 23:42:51 +00:00
pageSize? : number ;
2022-08-25 21:18:13 +00:00
onQueryChange ? :
| ( ( queryData : ITableQueryData ) = > void )
| ( ( queryData : ITableQueryData ) = > number ) ;
2025-01-10 15:37:55 +00:00
customControl ? : ( ) = > JSX . Element | null ;
2024-08-21 13:43:27 +00:00
/** Filter button right of the search rendering alternative responsive design where search bar moves to new line but filter button remains inline with other table headers */
customFiltersButton ? : ( ) = > JSX . Element ;
2022-04-07 19:12:38 +00:00
stackControls? : boolean ;
2022-11-22 16:13:33 +00:00
onSelectSingleRow ? : ( value : Row | IRowProps ) = > void ;
2024-03-04 13:14:50 +00:00
/ * * T h i s i s c a l l e d w h e n y o u c l i c k o n a r o w . T h i s w a s a d d e d a s ` o n S e l e c t S i n g l e R o w `
* only work if ` disableMultiRowSelect ` is also set to ` true ` . TODO : figure out
* if we want to keep this
* /
onClickRow ? : ( row : T ) = > void ;
2024-12-18 15:12:27 +00:00
/** Used if users can click the row and another child element does not have the same onClick functionality */
2024-12-26 22:51:28 +00:00
keyboardSelectableRows? : boolean ;
2024-03-04 13:14:50 +00:00
/ * * U s e f o r c l i e n t s i d e f i l t e r i n g : U s e k e y g l o b a l f o r f i l t e r i n g o n a n y c o l u m n , o r u s e c o l u m n i d a s
* key * /
2022-01-31 22:41:54 +00:00
filters? : Record < string , string | number | boolean > ;
2022-01-31 22:24:20 +00:00
renderCount ? : ( ) = > JSX . Element | null ;
2024-08-21 20:12:42 +00:00
/ * * O p t i o n a l h e l p t e x t t o r e n d e r o n b o t t o m - l e f t o f t h e t a b l e . H i d d e n w h e n t a b l e i s l o a d i n g a n d n o
* rows of data are present . * /
renderTableHelpText ? : ( ) = > JSX . Element | null ;
2022-06-13 23:20:57 +00:00
setExportRows ? : ( rows : Row [ ] ) = > void ;
2023-03-14 20:03:02 +00:00
disableTableHeader? : boolean ;
2024-10-25 10:27:44 +00:00
/** Set to true to persist the row selections across table data filters */
persistSelectedRows? : boolean ;
2024-10-09 15:09:38 +00:00
/** handler called when the `clear selection` button is called */
onClearSelection ? : ( ) = > void ;
2021-04-04 12:45:24 +00:00
}
2021-04-14 16:52:15 +00:00
const baseClass = "table-container" ;
2021-04-04 12:45:24 +00:00
2022-08-15 17:00:06 +00:00
const DEFAULT_PAGE_SIZE = 20 ;
2021-04-04 12:45:24 +00:00
const DEFAULT_PAGE_INDEX = 0 ;
2024-03-04 13:14:50 +00:00
const TableContainer = < T , > ( {
2023-12-09 00:54:24 +00:00
columnConfigs ,
2021-07-10 17:29:27 +00:00
data ,
2022-01-31 22:41:54 +00:00
filters ,
2021-07-10 17:29:27 +00:00
isLoading ,
2021-08-03 19:42:48 +00:00
manualSortBy = false ,
2023-01-04 16:41:15 +00:00
defaultSearchQuery = "" ,
2025-03-31 15:44:04 +00:00
pageIndex = DEFAULT_PAGE_INDEX ,
2021-09-10 19:06:37 +00:00
defaultSortHeader = "name" ,
defaultSortDirection = "asc" ,
2024-10-25 10:27:44 +00:00
defaultSelectedRows ,
2021-09-10 19:06:37 +00:00
inputPlaceHolder = "Search" ,
2021-07-10 17:29:27 +00:00
additionalQueries ,
resultsTitle ,
emptyComponent ,
className ,
disableActionButton ,
2021-09-10 19:06:37 +00:00
disableMultiRowSelect = false ,
2023-05-11 19:25:56 +00:00
actionButton ,
2021-07-10 17:29:27 +00:00
showMarkAllPages ,
isAllPagesSelected ,
toggleAllPagesSelected ,
2021-07-15 17:01:52 +00:00
searchable ,
wideSearch ,
disablePagination ,
2022-02-08 00:52:55 +00:00
disableNextPage ,
2021-07-19 18:02:41 +00:00
disableCount ,
2023-05-11 19:25:56 +00:00
primarySelectAction ,
2021-07-26 17:07:27 +00:00
secondarySelectActions ,
2021-10-14 15:11:53 +00:00
searchToolTipText ,
2021-11-05 04:16:42 +00:00
isClientSidePagination ,
2023-04-27 13:24:02 +00:00
onClientSidePaginationChange ,
2021-11-09 17:31:28 +00:00
isClientSideFilter ,
2022-06-13 23:20:57 +00:00
isMultiColumnFilter ,
2022-11-02 18:18:08 +00:00
disableHighlightOnHover ,
2021-11-15 23:42:51 +00:00
pageSize = DEFAULT_PAGE_SIZE ,
2021-11-09 17:31:28 +00:00
selectedDropdownFilter ,
searchQueryColumn ,
2021-12-22 17:07:12 +00:00
onQueryChange ,
customControl ,
2024-08-21 13:43:27 +00:00
customFiltersButton ,
2022-04-07 19:12:38 +00:00
stackControls ,
2021-12-22 17:07:12 +00:00
onSelectSingleRow ,
2024-03-04 13:14:50 +00:00
onClickRow ,
2024-12-26 22:51:28 +00:00
keyboardSelectableRows ,
2021-12-29 22:22:18 +00:00
renderCount ,
2024-08-21 20:12:42 +00:00
renderTableHelpText ,
2022-06-13 23:20:57 +00:00
setExportRows ,
2023-03-14 20:03:02 +00:00
disableTableHeader ,
2024-10-25 10:27:44 +00:00
persistSelectedRows ,
2024-10-09 15:09:38 +00:00
onClearSelection = noop ,
2024-03-04 13:14:50 +00:00
} : ITableContainerProps < T > ) = > {
2023-01-04 16:41:15 +00:00
const [ searchQuery , setSearchQuery ] = useState ( defaultSearchQuery ) ;
2021-04-14 16:52:15 +00:00
const [ sortHeader , setSortHeader ] = useState ( defaultSortHeader || "" ) ;
const [ sortDirection , setSortDirection ] = useState (
defaultSortDirection || ""
) ;
2025-03-31 15:44:04 +00:00
const [ currentPageIndex , setCurrentPageIndex ] = useState < number > ( pageIndex ) ;
2021-11-09 17:31:28 +00:00
const [ clientFilterCount , setClientFilterCount ] = useState < number > ( ) ;
2021-04-04 12:45:24 +00:00
2023-04-27 13:24:02 +00:00
// Client side pagination is being overridden to previous page without this
useEffect ( ( ) = > {
2025-03-31 15:44:04 +00:00
if ( isClientSidePagination && currentPageIndex !== DEFAULT_PAGE_INDEX ) {
setCurrentPageIndex ( DEFAULT_PAGE_INDEX ) ;
2023-04-27 13:24:02 +00:00
}
2025-03-31 15:44:04 +00:00
} , [ currentPageIndex , isClientSidePagination ] ) ;
// pageIndex must update currentPageIndex anytime it's changed or else it causes bugs
// e.g. bug of filter dd not reverting table to page 0
useEffect ( ( ) = > {
if ( ! isClientSidePagination ) {
setCurrentPageIndex ( pageIndex ) ;
}
} , [ pageIndex , isClientSidePagination ] ) ;
2023-04-27 13:24:02 +00:00
2021-12-29 18:10:54 +00:00
const prevPageIndex = useRef ( 0 ) ;
2021-04-04 12:45:24 +00:00
const wrapperClasses = classnames ( baseClass , className ) ;
const EmptyComponent = emptyComponent ;
2021-04-14 16:52:15 +00:00
const onSortChange = useCallback (
( id? : string , isDesc? : boolean ) = > {
if ( id === undefined ) {
2021-08-16 21:02:00 +00:00
setSortHeader ( defaultSortHeader || "" ) ;
setSortDirection ( defaultSortDirection || "" ) ;
2021-04-14 16:52:15 +00:00
} else {
setSortHeader ( id ) ;
const direction = isDesc ? "desc" : "asc" ;
setSortDirection ( direction ) ;
}
} ,
2021-08-16 21:02:00 +00:00
[ defaultSortHeader , defaultSortDirection , setSortHeader , setSortDirection ]
2021-04-14 16:52:15 +00:00
) ;
2021-04-04 12:45:24 +00:00
const onSearchQueryChange = ( value : string ) = > {
2024-04-10 18:50:08 +00:00
setSearchQuery ( value . trim ( ) ) ;
2021-04-04 12:45:24 +00:00
} ;
2022-02-08 00:52:55 +00:00
const onPaginationChange = useCallback (
( newPage : number ) = > {
2023-04-27 13:24:02 +00:00
if ( ! isClientSidePagination ) {
2025-03-31 15:44:04 +00:00
setCurrentPageIndex ( newPage ) ;
2023-04-27 13:24:02 +00:00
}
2022-02-08 00:52:55 +00:00
} ,
2025-03-31 15:44:04 +00:00
[ isClientSidePagination ]
2022-02-08 00:52:55 +00:00
) ;
2021-04-04 12:45:24 +00:00
2021-09-30 19:32:06 +00:00
useDeepEffect ( ( ) = > {
2021-12-29 18:10:54 +00:00
if ( ! onQueryChange ) {
return ;
}
2021-04-04 12:45:24 +00:00
const queryData = {
searchQuery ,
sortHeader ,
sortDirection ,
pageSize ,
2025-03-31 15:44:04 +00:00
pageIndex : currentPageIndex ,
2021-04-04 12:45:24 +00:00
} ;
2021-09-30 19:32:06 +00:00
2025-03-31 15:44:04 +00:00
if ( prevPageIndex . current === currentPageIndex ) {
setCurrentPageIndex ( 0 ) ;
2021-09-10 19:06:37 +00:00
}
2021-12-29 18:10:54 +00:00
2022-08-25 21:18:13 +00:00
// NOTE: used to reset page number to 0 when modifying filters
const newPageIndex = onQueryChange ( queryData ) ;
if ( newPageIndex === 0 ) {
2025-03-31 15:44:04 +00:00
setCurrentPageIndex ( 0 ) ;
2022-08-25 21:18:13 +00:00
}
2021-12-29 18:10:54 +00:00
2025-03-31 15:44:04 +00:00
prevPageIndex . current = currentPageIndex ;
2021-04-14 16:52:15 +00:00
} , [
searchQuery ,
sortHeader ,
sortDirection ,
pageSize ,
2025-03-31 15:44:04 +00:00
currentPageIndex ,
2021-04-14 16:52:15 +00:00
additionalQueries ,
] ) ;
2021-04-04 12:45:24 +00:00
2025-03-31 15:44:04 +00:00
/ * * T h i s i s s e r v e r s i d e p a g i n a t i o n . C l i e n t s i d e p a g i n a t i o n i s h a n d l e d i n
* data table using react - table builtins * /
const renderServersidePagination = useCallback ( ( ) = > {
2022-01-31 22:24:20 +00:00
if ( disablePagination || isClientSidePagination ) {
return null ;
}
return (
< Pagination
2025-03-20 16:40:43 +00:00
disablePrev = { pageIndex === 0 }
disableNext = { disableNextPage || data . length < pageSize }
onPrevPage = { ( ) = > onPaginationChange ( pageIndex - 1 ) }
onNextPage = { ( ) = > onPaginationChange ( pageIndex + 1 ) }
2025-03-31 15:44:04 +00:00
hidePagination = {
( disableNextPage || data . length < pageSize ) && pageIndex === 0
}
2022-01-31 22:24:20 +00:00
/ >
) ;
} , [
2022-02-08 00:52:55 +00:00
data ,
2022-01-31 22:24:20 +00:00
disablePagination ,
isClientSidePagination ,
2022-02-08 00:52:55 +00:00
disableNextPage ,
2022-01-31 22:24:20 +00:00
pageIndex ,
pageSize ,
onPaginationChange ,
] ) ;
2025-02-22 00:56:20 +00:00
const renderFilterActionButton = ( ) = > {
// always !!actionButton here, this is for type checker
if ( actionButton ) {
if ( actionButton . gitOpsModeCompatible ) {
return (
< GitOpsModeTooltipWrapper
tipOffset = { 8 }
renderChildren = { ( disableChildren ) = > (
< Button
disabled = { disableActionButton || disableChildren }
onClick = { actionButton . onActionButtonClick }
2025-04-16 13:56:09 +00:00
variant = { actionButton . variant || "default" }
2025-02-22 00:56:20 +00:00
className = { ` ${ baseClass } __table-action-button ` }
>
< >
{ actionButton . buttonText }
{ actionButton . iconSvg && < Icon name = { actionButton . iconSvg } / > }
< / >
< / Button >
) }
/ >
) ;
}
return (
< Button
disabled = { disableActionButton }
onClick = { actionButton . onActionButtonClick }
2025-04-16 13:56:09 +00:00
variant = { actionButton . variant || "default" }
2025-02-22 00:56:20 +00:00
className = { ` ${ baseClass } __table-action-button ` }
>
< >
{ actionButton . buttonText }
{ actionButton . iconSvg && < Icon name = { actionButton . iconSvg } / > }
< / >
< / Button >
) ;
}
// should never reach here
return null ;
} ;
2024-08-21 13:43:27 +00:00
const renderFilters = useCallback ( ( ) = > {
const opacity = isLoading ? { opacity : 0.4 } : { opacity : 1 } ;
2022-01-13 17:12:54 +00:00
2024-08-21 13:43:27 +00:00
// New preferred pattern uses grid container/box to allow for more dynamic responsiveness
2025-01-10 15:37:55 +00:00
// At low widths, right header stacks on top of left header
if ( stackControls ) {
2024-08-21 13:43:27 +00:00
return (
2024-08-20 13:41:49 +00:00
< div className = "container" >
2025-01-10 15:37:55 +00:00
< div className = "stackable-header" >
2024-06-14 17:12:56 +00:00
{ renderCount && ! disableCount && (
< div
className = { ` ${ baseClass } __results-count ${
stackControls ? "stack-table-controls" : ""
} ` }
style = { opacity }
>
{ renderCount ( ) }
< / div >
) }
2024-08-20 13:41:49 +00:00
< / div >
2025-01-10 15:37:55 +00:00
{ actionButton && ! actionButton . hideButton && (
2025-02-22 00:56:20 +00:00
< div className = "stackable-header" > { renderFilterActionButton ( ) } < / div >
2025-01-10 15:37:55 +00:00
) }
< div className = "stackable-header top-shift-header" >
2024-08-20 13:41:49 +00:00
{ customControl && customControl ( ) }
{ searchable && ! wideSearch && (
< div className = { ` ${ baseClass } __search ` } >
< div
className = { ` ${ baseClass } __search-input ` }
data - tip
data - for = "search-tooltip"
data - tip - disable = { ! searchToolTipText }
2023-03-14 20:03:02 +00:00
>
2024-08-20 13:41:49 +00:00
< SearchField
placeholder = { inputPlaceHolder }
defaultValue = { searchQuery }
onChange = { onSearchQueryChange }
/ >
< / div >
< ReactTooltip
effect = "solid"
backgroundColor = { COLORS [ "tooltip-bg" ] }
id = "search-tooltip"
data - html
>
< span className = { ` tooltip ${ baseClass } __tooltip-text ` } >
{ searchToolTipText }
< / span >
< / ReactTooltip >
< / div >
) }
2025-01-10 15:37:55 +00:00
{ customFiltersButton && customFiltersButton ( ) }
2023-03-14 20:03:02 +00:00
< / div >
2024-08-20 13:41:49 +00:00
< / div >
2024-08-21 13:43:27 +00:00
) ;
}
2025-03-12 15:26:12 +00:00
2024-08-21 13:43:27 +00:00
return (
< >
{ wideSearch && searchable && (
< div className = { ` ${ baseClass } __search-input wide-search ` } >
< SearchField
placeholder = { inputPlaceHolder }
defaultValue = { searchQuery }
onChange = { onSearchQueryChange }
/ >
< / div >
) }
{ ! disableTableHeader && (
< div
className = { ` ${ baseClass } __header ${
stackControls ? "stack-table-controls" : ""
} ` }
>
2024-08-20 13:41:49 +00:00
< div
2024-08-21 13:43:27 +00:00
className = { ` ${ baseClass } __header-left ${
2024-08-20 13:41:49 +00:00
stackControls ? "stack-table-controls" : ""
} ` }
>
2024-08-21 13:43:27 +00:00
{ renderCount && ! disableCount && (
< div
className = { ` ${ baseClass } __results-count ${
stackControls ? "stack-table-controls" : ""
} ` }
style = { opacity }
>
{ renderCount ( ) }
< / div >
) }
< span className = "controls" >
2025-02-22 00:56:20 +00:00
{ actionButton &&
! actionButton . hideButton &&
renderFilterActionButton ( ) }
2024-08-21 13:43:27 +00:00
{ customControl && customControl ( ) }
< / span >
< / div >
2024-08-20 13:41:49 +00:00
2024-08-21 13:43:27 +00:00
{ /* Render search bar only if not empty component */ }
{ searchable && ! wideSearch && (
< div className = { ` ${ baseClass } __search ` } >
< div
className = { ` ${ baseClass } __search-input ${
stackControls ? "stack-table-controls" : ""
} ` }
data - tip
data - for = "search-tooltip"
data - tip - disable = { ! searchToolTipText }
>
< SearchField
placeholder = { inputPlaceHolder }
defaultValue = { searchQuery }
onChange = { onSearchQueryChange }
/ >
2024-08-20 13:41:49 +00:00
< / div >
2024-08-21 13:43:27 +00:00
< ReactTooltip
effect = "solid"
backgroundColor = { COLORS [ "tooltip-bg" ] }
id = "search-tooltip"
data - html
>
< span className = { ` tooltip ${ baseClass } __tooltip-text ` } >
{ searchToolTipText }
< / span >
< / ReactTooltip >
< / div >
) }
< / div >
) }
< / >
) ;
} , [
actionButton ,
customControl ,
customFiltersButton ,
disableActionButton ,
disableCount ,
disableTableHeader ,
inputPlaceHolder ,
isLoading ,
renderCount ,
searchQuery ,
searchToolTipText ,
searchable ,
stackControls ,
wideSearch ,
] ) ;
2024-08-20 13:41:49 +00:00
2024-08-21 13:43:27 +00:00
return (
< div className = { wrapperClasses } >
{ renderFilters ( ) }
2022-04-07 19:12:38 +00:00
< div className = { ` ${ baseClass } __data-table-block ` } >
2021-04-04 12:45:24 +00:00
{ /* No entities for this result. */ }
2022-06-13 23:20:57 +00:00
{ ( ! isLoading && data . length === 0 && ! isMultiColumnFilter ) ||
2024-01-17 15:35:58 +00:00
( searchQuery . length &&
data . length === 0 &&
! isMultiColumnFilter &&
! isLoading ) ? (
2021-08-11 21:37:36 +00:00
< >
2025-03-31 15:44:04 +00:00
< EmptyComponent pageIndex = { currentPageIndex } / >
2025-03-20 16:40:43 +00:00
{ /* This UI only shows if a user navigates to a table page with a URL page param that is outside the # of pages available */ }
2025-03-31 15:44:04 +00:00
{ currentPageIndex !== 0 && (
2021-08-11 21:37:36 +00:00
< div className = { ` ${ baseClass } __empty-page ` } >
2025-03-20 16:40:43 +00:00
< div className = { ` ${ baseClass } __previous-button ` } >
2021-08-11 21:37:36 +00:00
< Pagination
2025-03-20 16:40:43 +00:00
disableNext
2025-03-31 15:44:04 +00:00
onNextPage = { ( ) = > onPaginationChange ( currentPageIndex + 1 ) }
onPrevPage = { ( ) = > onPaginationChange ( currentPageIndex - 1 ) }
2021-08-11 21:37:36 +00:00
/ >
< / div >
< / div >
) }
< / >
2021-04-14 16:52:15 +00:00
) : (
2021-04-04 12:45:24 +00:00
< >
2021-12-09 22:43:34 +00:00
{ / * T O D O : F i x t h i s h a c k y s o l u t i o n t o c l i e n t s i d e s e a r c h b e i n g 0 r e n d e r i n g e m p t y c o m p o n e n t b u t
no longer accesses rows . length because DataTable is not rendered * / }
2024-01-17 15:35:58 +00:00
{ ! isLoading && clientFilterCount === 0 && ! isMultiColumnFilter && (
2025-03-31 15:44:04 +00:00
< EmptyComponent pageIndex = { currentPageIndex } / >
2021-12-09 22:43:34 +00:00
) }
< div
className = {
2022-06-13 23:20:57 +00:00
isClientSideFilter && ! isMultiColumnFilter
2021-12-09 22:43:34 +00:00
? ` client-result-count- ${ clientFilterCount } `
: ""
2021-07-29 19:47:04 +00:00
}
2021-12-09 22:43:34 +00:00
>
< DataTable
isLoading = { isLoading }
2023-12-09 00:54:24 +00:00
columns = { columnConfigs }
2021-12-09 22:43:34 +00:00
data = { data }
2022-01-31 22:41:54 +00:00
filters = { filters }
2021-12-09 22:43:34 +00:00
manualSortBy = { manualSortBy }
sortHeader = { sortHeader }
sortDirection = { sortDirection }
onSort = { onSortChange }
disableMultiRowSelect = { disableMultiRowSelect }
showMarkAllPages = { showMarkAllPages }
isAllPagesSelected = { isAllPagesSelected }
toggleAllPagesSelected = { toggleAllPagesSelected }
resultsTitle = { resultsTitle }
defaultPageSize = { pageSize }
2025-03-31 15:44:04 +00:00
defaultPageIndex = { pageIndex }
2024-10-25 10:27:44 +00:00
defaultSelectedRows = { defaultSelectedRows }
2023-05-11 19:25:56 +00:00
primarySelectAction = { primarySelectAction }
2021-12-09 22:43:34 +00:00
secondarySelectActions = { secondarySelectActions }
onSelectSingleRow = { onSelectSingleRow }
2024-03-04 13:14:50 +00:00
onClickRow = { onClickRow }
2024-12-26 22:51:28 +00:00
keyboardSelectableRows = { keyboardSelectableRows }
2024-05-23 20:30:24 +00:00
onResultsCountChange = { setClientFilterCount }
2021-12-09 22:43:34 +00:00
isClientSidePagination = { isClientSidePagination }
2023-04-27 13:24:02 +00:00
onClientSidePaginationChange = { onClientSidePaginationChange }
2021-12-09 22:43:34 +00:00
isClientSideFilter = { isClientSideFilter }
2022-11-02 18:18:08 +00:00
disableHighlightOnHover = { disableHighlightOnHover }
2021-12-09 22:43:34 +00:00
searchQuery = { searchQuery }
searchQueryColumn = { searchQueryColumn }
selectedDropdownFilter = { selectedDropdownFilter }
2024-08-21 20:12:42 +00:00
renderTableHelpText = { renderTableHelpText }
Live query performance improvements (#11995)
## Addresses #11856
Improve performance of the rendering of live query results by:
- rendering the table on a set interval instead of with each new result
- preventing redundant rerenders of various sorts
Partial run, with memory leak:
<img width="2552" alt="partial run with memory leak, after smaller
optimizations, before debouncing queryResults"
src="https://github.com/fleetdm/fleet/assets/61553566/5288bffb-6940-43da-9083-59adb4a25916">
Full run after debounce, no memory leak (10x improvement of max JS heap
size):
<img width="2559" alt="full run after debounce, no memory leak"
src="https://github.com/fleetdm/fleet/assets/61553566/be056610-e7a5-4289-a433-1070cf016e83">
**NOTE** - there are further optimizations to try on this page, and the
debounce interval can potentially be shortened to improve UX. In
experimenting with that, it's not immediately clear what a good balance
of UX / performance is. Since the customer seems keen to solve this, I
think we should merge as-is and send them a demo build to confirm this
fixes their problem, then iterate once they've confirmed it does.
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
- [x] Changes file added for user-visible changes in `changes/`
- [x] Manual QA for all new/changed functionality
---------
Co-authored-by: Lucas Rodriguez <lucas@fleetdm.com>
Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
Co-authored-by: Sarah Gillespie <sarah@fleetdm.com>
2023-06-06 20:23:09 +00:00
renderPagination = {
2025-03-31 15:44:04 +00:00
isClientSidePagination
? undefined
: renderServersidePagination
Live query performance improvements (#11995)
## Addresses #11856
Improve performance of the rendering of live query results by:
- rendering the table on a set interval instead of with each new result
- preventing redundant rerenders of various sorts
Partial run, with memory leak:
<img width="2552" alt="partial run with memory leak, after smaller
optimizations, before debouncing queryResults"
src="https://github.com/fleetdm/fleet/assets/61553566/5288bffb-6940-43da-9083-59adb4a25916">
Full run after debounce, no memory leak (10x improvement of max JS heap
size):
<img width="2559" alt="full run after debounce, no memory leak"
src="https://github.com/fleetdm/fleet/assets/61553566/be056610-e7a5-4289-a433-1070cf016e83">
**NOTE** - there are further optimizations to try on this page, and the
debounce interval can potentially be shortened to improve UX. In
experimenting with that, it's not immediately clear what a good balance
of UX / performance is. Since the customer seems keen to solve this, I
think we should merge as-is and send them a demo build to confirm this
fixes their problem, then iterate once they've confirmed it does.
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
- [x] Changes file added for user-visible changes in `changes/`
- [x] Manual QA for all new/changed functionality
---------
Co-authored-by: Lucas Rodriguez <lucas@fleetdm.com>
Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
Co-authored-by: Sarah Gillespie <sarah@fleetdm.com>
2023-06-06 20:23:09 +00:00
}
2022-06-13 23:20:57 +00:00
setExportRows = { setExportRows }
2024-10-09 15:09:38 +00:00
onClearSelection = { onClearSelection }
2024-10-25 10:27:44 +00:00
persistSelectedRows = { persistSelectedRows }
2021-07-15 17:01:52 +00:00
/ >
2021-12-09 22:43:34 +00:00
< / div >
2021-04-04 12:45:24 +00:00
< / >
2021-04-14 16:52:15 +00:00
) }
2021-04-04 12:45:24 +00:00
< / div >
< / div >
) ;
} ;
export default TableContainer ;