-
Notifications
You must be signed in to change notification settings - Fork 919
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
28 changed files
with
1,963 additions
and
630 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
130 changes: 130 additions & 0 deletions
130
opencti-platform/opencti-front/src/components/dataGrid/DataTable.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import React, { ReactNode, useCallback, useRef, useState } from 'react'; | ||
import ToolBar from '@components/data/ToolBar'; | ||
import * as R from 'ramda'; | ||
import DataTableFilters, { DataTableDisplayFilters } from './DataTableFilters'; | ||
import SearchInput from '../SearchInput'; | ||
import { DataTableLineDummy } from './DataTableLine'; | ||
import useEntityToggle from '../../utils/hooks/useEntityToggle'; | ||
import DataTableBody from './DataTableBody'; | ||
import { DataTableContext, defaultColumnsMap } from './dataTableUtils'; | ||
import { DataTableColumn, DataTableColumns, DataTableContextProps, DataTableProps, LocalStorageColumns } from './dataTableTypes'; | ||
import useLocalStorage, { usePaginationLocalStorage } from '../../utils/hooks/useLocalStorage'; | ||
|
||
const DataTable = ({ | ||
dataColumns, | ||
filterExportContext, | ||
resolvePath, | ||
storageKey, | ||
initialValues, | ||
toolbarFilters, | ||
availableFilterKeys, | ||
searchContextFinal, | ||
availableEntityTypes, | ||
availableRelationshipTypes, | ||
availableRelationFilterTypes, | ||
preloadedPaginationProps, | ||
parametersWithPadding = false, | ||
}: DataTableProps) => { | ||
const memoizedHeaders = useRef<ReactNode>(null); | ||
const setMemoizedHeaders = useCallback((headers: ReactNode) => { | ||
memoizedHeaders.current = headers; | ||
}, []); | ||
|
||
const { | ||
viewStorage: { | ||
searchTerm, | ||
}, | ||
helpers, | ||
paginationOptions, | ||
} = usePaginationLocalStorage(storageKey, initialValues); | ||
|
||
const localStorageColumns = useLocalStorage<LocalStorageColumns>(`${storageKey}_columns`, {}, true)[0]; | ||
|
||
const { | ||
selectedElements, | ||
deSelectedElements, | ||
selectAll, | ||
handleClearSelectedElements, | ||
numberOfSelectedElements, | ||
} = useEntityToggle(storageKey); | ||
|
||
const [columns, setColumns] = useState<DataTableColumns>([ | ||
{ id: 'select', visible: true } as DataTableColumn, | ||
...Object.entries(dataColumns).map(([id, column], index) => { | ||
const currentColumn = localStorageColumns?.[id]; | ||
return R.mergeDeepRight(defaultColumnsMap.get(id) as DataTableColumn, { | ||
...column, | ||
id, | ||
order: currentColumn?.index ?? index, | ||
visible: currentColumn?.visible ?? true, | ||
...(currentColumn?.size ? { size: currentColumn?.size } : {}), | ||
}); | ||
}), | ||
{ id: 'navigate', visible: true } as DataTableColumn, | ||
]); | ||
|
||
return ( | ||
<DataTableContext.Provider | ||
value={{ | ||
storageKey, | ||
columns, | ||
effectiveColumns: columns.filter(({ visible }) => visible).sort((a, b) => a.order! - b.order!), | ||
initialValues, | ||
setColumns, | ||
resolvePath, | ||
parametersWithPadding, | ||
availableFilterKeys, | ||
paginationOptions, | ||
filterExportContext, | ||
} as DataTableContextProps} | ||
> | ||
<div style={{ display: 'flex', marginTop: -10 }}> | ||
<SearchInput | ||
variant={'small'} | ||
onSubmit={helpers.handleSearch} | ||
keyword={searchTerm} | ||
/> | ||
<DataTableFilters | ||
availableFilterKeys={availableFilterKeys} | ||
searchContextFinal={searchContextFinal} | ||
availableEntityTypes={availableEntityTypes} | ||
availableRelationshipTypes={availableRelationshipTypes} | ||
availableRelationFilterTypes={availableRelationFilterTypes} | ||
/> | ||
</div> | ||
<DataTableDisplayFilters | ||
availableFilterKeys={availableFilterKeys} | ||
availableRelationFilterTypes={availableRelationFilterTypes} | ||
/> | ||
<React.Suspense | ||
fallback={( | ||
<> | ||
{memoizedHeaders.current} | ||
{Array(10) | ||
.fill(0) | ||
.map((_, idx) => ( | ||
<DataTableLineDummy key={idx} /> | ||
))} | ||
</> | ||
)} | ||
> | ||
<DataTableBody | ||
preloadedPaginationProps={preloadedPaginationProps} | ||
columns={columns.slice(1, -1)} | ||
setMemoizedHeaders={setMemoizedHeaders} | ||
/> | ||
</React.Suspense> | ||
<ToolBar | ||
selectedElements={selectedElements} | ||
deSelectedElements={deSelectedElements} | ||
numberOfSelectedElements={numberOfSelectedElements} | ||
selectAll={selectAll} | ||
search={searchTerm} | ||
filters={toolbarFilters} | ||
handleClearSelectedElements={handleClearSelectedElements} | ||
/> | ||
</DataTableContext.Provider> | ||
); | ||
}; | ||
|
||
export default DataTable; |
121 changes: 121 additions & 0 deletions
121
opencti-platform/opencti-front/src/components/dataGrid/DataTableBody.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import React, { useCallback, useContext, useEffect, useLayoutEffect, useMemo, useState } from 'react'; | ||
import makeStyles from '@mui/styles/makeStyles'; | ||
import { createStyles } from '@mui/styles'; | ||
import usePreloadedPaginationFragment from '../../utils/hooks/usePreloadedPaginationFragment'; | ||
import DataTableHeaders, { MemoizedTableHeaders } from './DataTableHeaders'; | ||
import DataTableLines from './DataTableLines'; | ||
import { DataTableContext } from './dataTableUtils'; | ||
import { ColumnSizeVars, DataTableBodyProps } from './dataTableTypes'; | ||
import type { Theme } from '../Theme'; | ||
import useLocalStorage from '../../utils/hooks/useLocalStorage'; | ||
import { isNotEmptyField } from '../../utils/utils'; | ||
|
||
const SELECT_COLUMN_SIZE = 42; | ||
|
||
const useStyles = makeStyles<Theme, { columnSizeVars: ColumnSizeVars }>(() => createStyles({ | ||
tableContainer: { | ||
height: '83%', | ||
}, | ||
linesContainer: ({ columnSizeVars }) => ({ | ||
...columnSizeVars, | ||
minWidth: '100%', | ||
height: '100%', | ||
overflow: 'auto', | ||
}), | ||
})); | ||
|
||
const DataTableBody = ({ | ||
setMemoizedHeaders, | ||
columns, | ||
preloadedPaginationProps, | ||
}: DataTableBodyProps) => { | ||
const { resolvePath, storageKey } = useContext(DataTableContext); | ||
|
||
const effectiveColumns = useMemo(() => columns.filter(({ visible }) => visible), [columns]); | ||
|
||
// QUERY PART | ||
const { data: queryData, hasMore, loadMore, isLoading } = usePreloadedPaginationFragment(preloadedPaginationProps); | ||
|
||
const fetchMore = (number = 10) => { | ||
if (!hasMore()) { | ||
return; | ||
} | ||
loadMore(number); | ||
}; | ||
|
||
const resolvedData = useMemo(() => { | ||
return resolvePath(queryData); | ||
}, [queryData, resolvePath]); | ||
|
||
// TABLE HANDLING | ||
const [resize, setResize] = useState(false); | ||
const [computeState, setComputeState] = useState<HTMLDivElement | null>(null); | ||
const bottomReached = useCallback(() => { | ||
const { scrollHeight, scrollTop: newScrollTop, clientHeight } = computeState as HTMLDivElement; | ||
if (scrollHeight - newScrollTop - clientHeight < 300 && !isLoading) { | ||
fetchMore(); | ||
} | ||
}, [fetchMore, isLoading]); | ||
|
||
const [localStorageColumns] = useLocalStorage(`${storageKey}_columns`, {}, true); | ||
// This is intended to improve performance by memoizing the column sizes | ||
const columnSizeVars: ColumnSizeVars = React.useMemo(() => { | ||
const localColumns: Record<string, { size: number }> = {}; | ||
const colSizes: { [key: string]: number } = { | ||
'--header-select-size': SELECT_COLUMN_SIZE, | ||
'--col-select-size': SELECT_COLUMN_SIZE, | ||
'--header-navigate-size': SELECT_COLUMN_SIZE, | ||
'--col-navigate-size': SELECT_COLUMN_SIZE, | ||
}; | ||
if (!computeState) { | ||
return colSizes; | ||
} | ||
const clientWidth = computeState.clientWidth - (2 * SELECT_COLUMN_SIZE); | ||
for (let i = 0; i < effectiveColumns.length; i += 1) { | ||
const column = effectiveColumns[i]!; | ||
let size = column.size ?? 200; | ||
if ((!column.size || resize) && (column.flexSize && Boolean(computeState))) { | ||
size = column.flexSize * (clientWidth / 100); | ||
column.size = size; | ||
} | ||
localColumns[column.id] = { size }; | ||
colSizes[`--header-${column.id}-size`] = size; | ||
colSizes[`--col-${column.id}-size`] = size; | ||
} | ||
if (isNotEmptyField(localColumns)) { | ||
setResize(false); | ||
} | ||
return colSizes; | ||
}, [resize, computeState, effectiveColumns, localStorageColumns]); | ||
const classes = useStyles({ columnSizeVars }); | ||
|
||
useEffect(() => { | ||
setMemoizedHeaders(<MemoizedTableHeaders columnSizeVars={columnSizeVars} />); | ||
}, [columnSizeVars]); | ||
|
||
useLayoutEffect(() => { | ||
const handleResize = () => setResize(true); | ||
window.addEventListener('resize', handleResize); | ||
return () => window.removeEventListener('resize', handleResize); | ||
}); | ||
|
||
return ( | ||
<div className={classes.tableContainer}> | ||
<DataTableHeaders columnSizeVars={columnSizeVars} /> | ||
<div | ||
ref={(node) => setComputeState(node)} | ||
onScroll={bottomReached} | ||
className={classes.linesContainer} | ||
style={{ ...columnSizeVars }} | ||
> | ||
{/* If we have perf issues we should find a way to memoize this */} | ||
<DataTableLines | ||
rows={resolvedData} | ||
isLoading={isLoading} | ||
/> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default DataTableBody; |
Oops, something went wrong.