Skip to content

Commit

Permalink
[backend/frontend] Implement full text search for documents (#1483)
Browse files Browse the repository at this point in the history
Co-authored-by: marie flores <[email protected]>
  • Loading branch information
SouadHadjiat and marieflorescontact authored Nov 9, 2023
1 parent 2b6a9a2 commit 184383d
Show file tree
Hide file tree
Showing 50 changed files with 2,591 additions and 69 deletions.
51 changes: 51 additions & 0 deletions opencti-platform/opencti-front/src/components/ItemEntityType.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import Chip from '@mui/material/Chip';
import React, { FunctionComponent } from 'react';
import makeStyles from '@mui/styles/makeStyles';
import { hexToRGB, itemColor } from '../utils/Colors';
import { useFormatter } from './i18n';

const useStyles = makeStyles(() => ({
chip: {
fontSize: 13,
lineHeight: '12px',
height: 20,
textTransform: 'uppercase',
borderRadius: '0',
width: 120,
},
chipInList: {
fontSize: 12,
height: 20,
float: 'left',
width: 120,
textTransform: 'uppercase',
borderRadius: '0',
},
}));

interface ItemEntityTypeProps {
entityType: string;
variant?: string;
}

const ItemEntityType: FunctionComponent<ItemEntityTypeProps> = ({
variant = 'inList',
entityType,
}) => {
const classes = useStyles();
const { t } = useFormatter();
const style = variant === 'inList' ? classes.chipInList : classes.chip;
return (
<Chip
classes={{ root: style }}
style={{
backgroundColor: hexToRGB(itemColor(entityType), 0.08),
color: itemColor(entityType),
border: `1px solid ${itemColor(entityType)}`,
}}
label={t(`entity_${entityType}`)}
/>
);
};

export default ItemEntityType;
3 changes: 3 additions & 0 deletions opencti-platform/opencti-front/src/components/ItemIcon.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import {
DatabaseExportOutline,
DiamondOutline,
FileDelimitedOutline,
FileOutline,
Fire,
HexagonOutline,
LabelOutline,
Expand Down Expand Up @@ -172,6 +173,8 @@ const iconSelector = (type, variant, fontSize, color, isReversed) => {
);
case 'label':
return <LabelOutline style={style} fontSize={fontSize} role="img" />;
case 'file':
return <FileOutline style={style} fontSize={fontSize} role="img" />;
case 'attack-pattern':
return <LockPattern style={style} fontSize={fontSize} role="img" />;
case 'campaign':
Expand Down
1 change: 1 addition & 0 deletions opencti-platform/opencti-front/src/private/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ const rootPrivateQuery = graphql`
id
enable
running
warning
}
enterprise_edition
...AppThemeProvider_settings
Expand Down
23 changes: 21 additions & 2 deletions opencti-platform/opencti-front/src/private/components/Search.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import React, { useState } from 'react';
import { KeyboardDoubleArrowDownOutlined } from '@mui/icons-material';
import Typography from '@mui/material/Typography';
import { SearchStixCoreObjectLineDummy } from '@components/search/SearchStixCoreObjectLine';
import {
Expand All @@ -9,6 +10,8 @@ import {
SearchStixCoreObjectsLinesPaginationQuery$variables,
} from '@components/search/__generated__/SearchStixCoreObjectsLinesPaginationQuery.graphql';
import { useParams } from 'react-router-dom';
import Button from '@mui/material/Button';
import SearchIndexedFiles from '@components/search/SearchIndexedFiles';
import TopBar from './nav/TopBar';
import ListLines from '../../components/list_lines/ListLines';
import ToolBar from './data/ToolBar';
Expand All @@ -25,7 +28,7 @@ const LOCAL_STORAGE_KEY = 'view-search';

const Search = () => {
const {
platformModuleHelpers: { isRuntimeFieldEnable },
platformModuleHelpers: { isRuntimeFieldEnable, isFileIndexManagerEnable },
} = useAuth();
const { t } = useFormatter();
const { keyword } = useParams() as { keyword: string };
Expand All @@ -35,6 +38,7 @@ const Search = () => {
} catch (e) {
// Do nothing
}
const fileSearchEnabled = isFileIndexManagerEnable();
const { viewStorage, helpers: storageHelpers, paginationOptions } = usePaginationLocalStorage<SearchStixCoreObjectsLinesPaginationQuery$variables>(
LOCAL_STORAGE_KEY,
{
Expand Down Expand Up @@ -65,6 +69,10 @@ const Search = () => {
searchStixCoreObjectsLinesQuery,
{ ...paginationOptions, search: searchTerm },
);
const [searchOpen, setSearchOpen] = useState(false);
const handleSearchIndexFiles = () => {
setSearchOpen(true);
};

const renderLines = () => {
const isRuntimeSort = isRuntimeFieldEnable() ?? false;
Expand Down Expand Up @@ -196,6 +204,17 @@ const Search = () => {
{t('Search for an entity')}
</Typography>
{renderLines()}
{fileSearchEnabled && searchTerm && (
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<Button
size="small"
onClick={handleSearchIndexFiles}
>
<KeyboardDoubleArrowDownOutlined /> {t('Extend this search to indexed files')} <KeyboardDoubleArrowDownOutlined />
</Button>
</div>
)}
{ searchOpen ? (<SearchIndexedFiles search={searchTerm}/>) : ('')}
</div>
</ExportContextProvider>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,26 @@ const TopMenuSettings = () => {
{t('Activity')}
</Button>
</Security>
<Security needs={[SETTINGS]}>
<Button
component={Link}
size="small"
to="/dashboard/settings/file_indexing"
variant={
location.pathname.includes('/dashboard/settings/file_indexing')
? 'contained'
: 'text'
}
color={
location.pathname.includes('/dashboard/settings/file_indexing')
? 'secondary'
: 'primary'
}
classes={{ root: classes.button }}
>
{t('File indexing')}
</Button>
</Security>
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import React, { FunctionComponent } from 'react';
import { Link } from 'react-router-dom';
import { createFragmentContainer, graphql } from 'react-relay';
import ListItem from '@mui/material/ListItem';
import ListItemText from '@mui/material/ListItemText';
import makeStyles from '@mui/styles/makeStyles';
import { SearchIndexedFileLine_node$data } from '@components/search/__generated__/SearchIndexedFileLine_node.graphql';
import ListItemIcon from '@mui/material/ListItemIcon';
import { OpenInNewOutlined } from '@mui/icons-material';
import IconButton from '@mui/material/IconButton';
import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction';
import Tooltip from '@mui/material/Tooltip';
import { DataColumns } from '../../../components/list_lines';
import { Theme } from '../../../components/Theme';
import { useFormatter } from '../../../components/i18n';
import ItemIcon from '../../../components/ItemIcon';
import { getFileUri } from '../../../utils/utils';
import { resolveLink } from '../../../utils/Entity';
import useGranted, { KNOWLEDGE_KNGETEXPORT, KNOWLEDGE_KNUPLOAD } from '../../../utils/hooks/useGranted';

const useStyles = makeStyles<Theme>((theme) => ({
item: {
paddingLeft: 10,
height: 50,
},
itemIcon: {
color: theme.palette.primary.main,
},
bodyItem: {
height: 20,
fontSize: 13,
float: 'left',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
paddingRight: 10,
},
}));

interface SearchIndexedFileLineComponentProps {
node: SearchIndexedFileLine_node$data;
dataColumns: DataColumns;
}

const SearchIndexedFileLineComponent: FunctionComponent<SearchIndexedFileLineComponentProps> = ({
node,
dataColumns,
}) => {
const classes = useStyles();
const { t } = useFormatter();
let entityLink = node.entity ? `${resolveLink(node.entity.entity_type)}/${node.entity.id}` : '';
const isGrantedToFiles = useGranted([KNOWLEDGE_KNUPLOAD, KNOWLEDGE_KNGETEXPORT]);
if (entityLink && isGrantedToFiles) {
entityLink = entityLink.concat('/files');
}
return (
<ListItem
classes={{ root: classes.item }}
divider={true}
button={true}
component="a"
href={getFileUri(node.file_id)}
target="_blank"
>
<ListItemIcon classes={{ root: classes.itemIcon }}>
<ItemIcon type="File" />
</ListItemIcon>
<ListItemText
primary={
<div>
{Object.values(dataColumns).map((value) => (
<div
key={value.label}
className={classes.bodyItem}
style={{ width: value.width }}
>
{value.render?.(node)}
</div>
))}
</div>
}
/>
<ListItemSecondaryAction>
{node.entity && entityLink && (
<Tooltip title={t('Open the entity overview in a separated tab')}>
<IconButton
component={Link}
target="_blank"
to={entityLink}
size="medium"
>
<OpenInNewOutlined fontSize="medium" />
</IconButton>
</Tooltip>
)}
</ListItemSecondaryAction>
</ListItem>
);
};

const SearchIndexedFileLine = createFragmentContainer(SearchIndexedFileLineComponent, {
node: graphql`
fragment SearchIndexedFileLine_node on IndexedFile {
id
name
uploaded_at
file_id
searchOccurrences
entity {
...on StixObject {
id
entity_type
representative {
main
}
}
...on StixCoreObject {
objectMarking {
edges {
node {
id
definition_type
definition
x_opencti_order
x_opencti_color
}
}
}
}
}
}
`,
});

export default SearchIndexedFileLine;
Loading

0 comments on commit 184383d

Please sign in to comment.