Skip to content

Commit

Permalink
[frontend] wip
Browse files Browse the repository at this point in the history
  • Loading branch information
Kedae committed Jan 20, 2024
1 parent 2fb4651 commit f7506b4
Show file tree
Hide file tree
Showing 28 changed files with 1,963 additions and 630 deletions.
1 change: 1 addition & 0 deletions opencti-platform/opencti-front/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ module.exports = {
'custom-rules',
],
rules: {
'@typescript-eslint/no-non-null-assertion': 'off',
'custom-rules/classes-rule': 1,
'no-restricted-syntax': 0,
'react/no-unused-prop-types': 0,
Expand Down
3 changes: 3 additions & 0 deletions opencti-platform/opencti-front/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@rjsf/core": "5.16.1",
"@rjsf/mui": "5.16.1",
"@rjsf/utils": "5.16.1",
"@types/react-beautiful-dnd": "^13.1.8",
"analytics": "0.8.9",
"apexcharts": "3.45.1",
"axios": "1.6.5",
Expand Down Expand Up @@ -50,10 +51,12 @@
"ramda": "0.29.1",
"react": "18.2.0",
"react-apexcharts": "1.4.1",
"react-beautiful-dnd": "13.1.1",
"react-color": "2.19.3",
"react-cookie": "7.0.1",
"react-csv": "2.2.2",
"react-dom": "18.2.0",
"react-draggable": "4.4.6",
"react-force-graph-2d": "1.25.3",
"react-force-graph-3d": "1.24.1",
"react-grid-layout": "1.4.4",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { handleFilterHelpers } from '../utils/hooks/useLocalStorage';
import { filterValuesContentQuery } from './FilterValuesContent';
import { FilterValuesContentQuery } from './__generated__/FilterValuesContentQuery.graphql';

interface FilterIconButtonProps {
availableFilterKeys?: string[];
export interface FilterIconButtonProps {
availableFilterKeys?: string[] | undefined;
filters?: FilterGroup;
handleRemoveFilter?: (key: string, op?: string) => void;
handleSwitchGlobalMode?: () => void;
Expand All @@ -20,7 +20,7 @@ interface FilterIconButtonProps {
disabledPossible?: boolean;
redirection?: boolean;
helpers?: handleFilterHelpers;
availableRelationFilterTypes?: Record<string, string[]>;
availableRelationFilterTypes?: Record<string, string[]> | undefined;
}

interface FilterIconButtonIfFiltersProps extends FilterIconButtonProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ const StyledBadge = styled(Badge)(() => ({
},
}));

const ItemMarkings = ({ variant, markingDefinitionsEdges, limit }) => {
const ItemMarkings = ({ variant, markingDefinitionsEdges, limit, handleAddFilter }) => {
const classes = useStyles();
const theme = useTheme();
const { t_i18n } = useFormatter();
Expand Down Expand Up @@ -139,6 +139,7 @@ const ItemMarkings = ({ variant, markingDefinitionsEdges, limit }) => {
border,
}}
label={markingDefinition.definition}
onClick={() => handleAddFilter('objectMarking', markingDefinition.id)}
/>
);
}
Expand Down Expand Up @@ -244,6 +245,7 @@ ItemMarkings.propTypes = {
variant: PropTypes.string,
limit: PropTypes.number,
markingDefinitions: PropTypes.array,
handleAddFilter: PropTypes.func,
};

export default ItemMarkings;
1 change: 1 addition & 0 deletions opencti-platform/opencti-front/src/components/ThemeDark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ const ThemeDark = (
MuiCssBaseline: {
styleOverrides: {
body: {
overflow: 'hidden',
scrollbarColor: '#6b6b6b #2b2b2b',
'&::-webkit-scrollbar, & *::-webkit-scrollbar': {
backgroundColor: paper || '#001e3c',
Expand Down
130 changes: 130 additions & 0 deletions opencti-platform/opencti-front/src/components/dataGrid/DataTable.tsx
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;
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;
Loading

0 comments on commit f7506b4

Please sign in to comment.