From be87c79f9c8e0a48ac2a0b45a79d6ab71e42df22 Mon Sep 17 00:00:00 2001 From: Gwendoline-FAVRE-FELIX Date: Mon, 17 Feb 2025 14:34:44 +0100 Subject: [PATCH] [frontend] Enable label click actions in the Knowledge view indicator list (#9292) --- .../EntityStixCoreRelationshipsIndicators.tsx | 1 - ...oreRelationshipsIndicatorsEntitiesView.tsx | 335 ++++++++++-------- .../StixDomainObjectIndicatorLine.jsx | 308 ---------------- .../StixDomainObjectIndicatorsLines.jsx | 153 -------- 4 files changed, 192 insertions(+), 605 deletions(-) delete mode 100644 opencti-platform/opencti-front/src/private/components/observations/indicators/StixDomainObjectIndicatorLine.jsx delete mode 100644 opencti-platform/opencti-front/src/private/components/observations/indicators/StixDomainObjectIndicatorsLines.jsx diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_core_relationships/views/indicators/EntityStixCoreRelationshipsIndicators.tsx b/opencti-platform/opencti-front/src/private/components/common/stix_core_relationships/views/indicators/EntityStixCoreRelationshipsIndicators.tsx index 7a9a9a895926..87bc2b87ef6c 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_core_relationships/views/indicators/EntityStixCoreRelationshipsIndicators.tsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_core_relationships/views/indicators/EntityStixCoreRelationshipsIndicators.tsx @@ -57,7 +57,6 @@ const EntityStixCoreRelationshipsIndicators: FunctionComponent} {view === 'relationships' diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_core_relationships/views/indicators/EntityStixCoreRelationshipsIndicatorsEntitiesView.tsx b/opencti-platform/opencti-front/src/private/components/common/stix_core_relationships/views/indicators/EntityStixCoreRelationshipsIndicatorsEntitiesView.tsx index e14565951ba0..22cd9ccab99e 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_core_relationships/views/indicators/EntityStixCoreRelationshipsIndicatorsEntitiesView.tsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_core_relationships/views/indicators/EntityStixCoreRelationshipsIndicatorsEntitiesView.tsx @@ -1,19 +1,29 @@ import React, { FunctionComponent } from 'react'; -import ListLines from '../../../../../../components/list_lines/ListLines'; -import ToolBar from '../../../../data/ToolBar'; -import useEntityToggle from '../../../../../../utils/hooks/useEntityToggle'; -import { useFormatter } from '../../../../../../components/i18n'; -import StixDomainObjectIndicatorsLines, { stixDomainObjectIndicatorsLinesQuery } from '../../../../observations/indicators/StixDomainObjectIndicatorsLines'; +import { graphql } from 'react-relay'; +import { + EntityStixCoreRelationshipsIndicatorsEntitiesViewQuery, + EntityStixCoreRelationshipsIndicatorsEntitiesViewQuery$variables, +} from '@components/common/stix_core_relationships/views/indicators/__generated__/EntityStixCoreRelationshipsIndicatorsEntitiesViewQuery.graphql'; +import { + EntityStixCoreRelationshipsIndicatorsEntitiesView_data$data, +} from '@components/common/stix_core_relationships/views/indicators/__generated__/EntityStixCoreRelationshipsIndicatorsEntitiesView_data.graphql'; +import Tooltip from '@mui/material/Tooltip'; +import { LibraryBooksOutlined } from '@mui/icons-material'; +import ToggleButton from '@mui/material/ToggleButton'; +import { Group, RelationManyToMany } from 'mdi-material-ui'; import Security from '../../../../../../utils/Security'; import { KNOWLEDGE_KNUPDATE } from '../../../../../../utils/hooks/useGranted'; import StixCoreRelationshipCreationFromEntity from '../../StixCoreRelationshipCreationFromEntity'; -import { PaginationLocalStorage } from '../../../../../../utils/hooks/useLocalStorage'; -import { DataColumns, PaginationOptions } from '../../../../../../components/list_lines'; -import { StixDomainObjectIndicatorsLinesQuery$data } from '../../../../observations/indicators/__generated__/StixDomainObjectIndicatorsLinesQuery.graphql'; +import { PaginationLocalStorage, usePaginationLocalStorage } from '../../../../../../utils/hooks/useLocalStorage'; +import { PaginationOptions } from '../../../../../../components/list_lines'; import useAuth from '../../../../../../utils/hooks/useAuth'; -import { QueryRenderer } from '../../../../../../relay/environment'; -import { isFilterGroupNotEmpty, useRemoveIdAndIncorrectKeysFromFilterGroupObject } from '../../../../../../utils/filters/filtersUtils'; +import { emptyFilterGroup, isFilterGroupNotEmpty, useRemoveIdAndIncorrectKeysFromFilterGroupObject } from '../../../../../../utils/filters/filtersUtils'; import { FilterGroup } from '../../../../../../utils/filters/filtersHelpers-types'; +import DataTable from '../../../../../../components/dataGrid/DataTable'; +import { DataTableVariant } from '../../../../../../components/dataGrid/dataTableTypes'; +import { useQueryLoadingWithLoadQuery } from '../../../../../../utils/hooks/useQueryLoading'; +import { UsePreloadedPaginationFragment } from '../../../../../../utils/hooks/usePreloadedPaginationFragment'; +import { useFormatter } from '../../../../../../components/i18n'; interface EntityStixCoreRelationshipsIndicatorsEntitiesViewProps { entityId: string @@ -23,9 +33,91 @@ interface EntityStixCoreRelationshipsIndicatorsEntitiesViewProps { localStorage: PaginationLocalStorage isRelationReversed: boolean currentView: string - enableContextualView: boolean, } +export const entityStixCoreRelationshipsIndicatorsEntitiesViewQuery = graphql` + query EntityStixCoreRelationshipsIndicatorsEntitiesViewQuery( + $search: String + $count: Int! + $cursor: ID + $orderBy: IndicatorsOrdering + $orderMode: OrderingMode + $filters: FilterGroup + ) { + ...EntityStixCoreRelationshipsIndicatorsEntitiesView_data + @arguments( + search: $search + count: $count + cursor: $cursor + filters: $filters + orderBy: $orderBy + orderMode: $orderMode + ) + } +`; + +export const entityStixCoreRelationshipsIndicatorsEntitiesViewFragment = graphql` + fragment EntityStixCoreRelationshipsIndicatorsEntitiesView_data on Query + @argumentDefinitions( + search: { type: "String" } + count: { type: "Int", defaultValue: 25 } + cursor: { type: "ID" } + filters: { type: "FilterGroup" } + orderBy: { type: "IndicatorsOrdering", defaultValue: valid_from } + orderMode: { type: "OrderingMode", defaultValue: desc } + ) + @refetchable(queryName: "EntityStixCoreRelationshipsIndicatorsEntitiesViewRefetchQuery") { + indicators( + search: $search + first: $count + after: $cursor + orderBy: $orderBy + orderMode: $orderMode + filters: $filters + ) @connection(key: "Pagination_indicators") { + edges { + node { + id + ...EntityStixCoreRelationshipsIndicatorsEntitiesViewLine_node + } + } + pageInfo { + endCursor + hasNextPage + globalCount + } + } + } +`; + +export const entityStixCoreRelationshipsIndicatorsEntitiesViewLineFragment = graphql` + fragment EntityStixCoreRelationshipsIndicatorsEntitiesViewLine_node on Indicator { + id + entity_type + name + pattern_type + description + valid_from + valid_until + created + created_at + x_opencti_score + x_opencti_main_observable_type + objectMarking { + id + definition_type + definition + x_opencti_order + x_opencti_color + } + objectLabel { + id + value + color + } + } +`; + const EntityStixCoreRelationshipsIndicatorsEntitiesView: FunctionComponent = ({ entityId, relationshipTypes, @@ -34,74 +126,41 @@ const EntityStixCoreRelationshipsIndicatorsEntitiesView: FunctionComponent { const { t_i18n } = useFormatter(); const { viewStorage, helpers: storageHelpers, localStorageKey } = localStorage; const { filters, searchTerm, - sortBy, orderAsc, - view, - numberOfElements, openExports, } = viewStorage; - const availableFilterKeys = [ - 'objectLabel', - 'objectMarking', - 'created', - 'valid_from', - 'valid_until', - 'x_opencti_score', - 'createdBy', - 'sightedBy', - 'x_opencti_detection', - 'based-on', - 'revoked', - 'creator_id', - 'confidence', - 'indicator_types', - 'pattern_type', - 'x_opencti_main_observable_type', - ]; - const { platformModuleHelpers } = useAuth(); const isRuntimeSort = platformModuleHelpers.isRuntimeFieldEnable(); - const dataColumns: DataColumns = { - pattern_type: { - label: 'Type', - width: '10%', - isSortable: true, - }, - name: { - label: 'Name', - width: '25%', - isSortable: true, - }, - objectLabel: { - label: 'Labels', - width: '15%', - isSortable: false, - }, - created_at: { - label: 'Platform creation date', - width: '15%', - isSortable: true, - }, - valid_until: { - label: 'Valid until', - width: '15%', - isSortable: true, - }, - objectMarking: { - label: 'Marking', - isSortable: isRuntimeSort, - width: '10%', - }, + const dataColumns = { + pattern_type: { percentWidth: 10 }, + name: { percentWidth: 30 }, + objectLabel: { percentWidth: 15 }, + created_at: { percentWidth: 15 }, + valid_until: { percentWidth: 20 }, + objectMarking: { percentWidth: 10, isSortable: isRuntimeSort }, + }; + + const initialValues = { + search: searchTerm, + orderBy: 'name', + orderMode: orderAsc ? 'asc' : 'desc', + filters: emptyFilterGroup, + view: currentView ?? 'entities', }; + const { paginationOptions } = usePaginationLocalStorage( + localStorageKey, + initialValues, + true, + ); + const userFilters = useRemoveIdAndIncorrectKeysFromFilterGroupObject(filters, ['Indicator']); const contextFilters: FilterGroup = { mode: 'and', @@ -118,90 +177,79 @@ const EntityStixCoreRelationshipsIndicatorsEntitiesView: FunctionComponent( + entityStixCoreRelationshipsIndicatorsEntitiesViewQuery, + queryPaginationOptions, + ); + + const refetch = React.useCallback(() => { + loadQuery(queryPaginationOptions, { fetchPolicy: 'store-and-network' }); + }, [queryRef]); + + const preloadedPaginationProps = { + linesQuery: entityStixCoreRelationshipsIndicatorsEntitiesViewQuery, + linesFragment: entityStixCoreRelationshipsIndicatorsEntitiesViewFragment, + queryRef, + nodePath: ['indicators', 'pageInfo', 'globalCount'], + setNumberOfElements: storageHelpers.handleSetNumberOfElements, + } as UsePreloadedPaginationFragment; + + const entitiesViewButton = ( + storageHelpers.handleChangeView('entities')}> + + + + + ); + const relationshipsView = ( + storageHelpers.handleChangeView('relationships')}> + + + + + ); + const knowledgeFromRelatedContainersView = ( + storageHelpers.handleChangeView('contextual')}> + + + + + ); + + const viewButtons = [entitiesViewButton, relationshipsView, knowledgeFromRelatedContainersView]; - const { - numberOfSelectedElements, - selectedElements, - deSelectedElements, - selectAll, - handleClearSelectedElements, - handleToggleSelectAll, - onToggleEntity, - } = useEntityToggle(localStorageKey); - - const finalView = currentView || view; return ( <> - - ( - - )} - /> - - (data.indicators?.edges ?? []).map((n) => n?.node)} + storageKey={localStorageKey} + initialValues={initialValues} + toolbarFilters={contextFilters} + preloadedPaginationProps={preloadedPaginationProps} + lineFragment={entityStixCoreRelationshipsIndicatorsEntitiesViewLineFragment} + exportContext={{ entity_type: 'Indicator' }} + additionalHeaderButtons={[...viewButtons]} /> + )} ({ - 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, - }, - goIcon: { - position: 'absolute', - right: -10, - }, - itemIconDisabled: { - color: theme.palette.grey[700], - }, - placeholder: { - display: 'inline-block', - height: '1em', - backgroundColor: theme.palette.grey[700], - }, -}); - -class StixDomainObjectIndicatorLineComponent extends Component { - render() { - const { - fsd, - classes, - dataColumns, - node, - onToggleEntity, - selectedElements, - deSelectedElements, - selectAll, - onToggleShiftEntity, - index, - } = this.props; - return ( - - (event.shiftKey - ? onToggleShiftEntity(index, node, event) - : onToggleEntity(node, event)) - } - > - - - - - - -
- -
-
- {node.name} -
-
- -
-
- {fsd(node.created_at)} -
-
- {fsd(node.valid_until)} -
-
- -
- - } - /> - - - -
- ); - } -} - -StixDomainObjectIndicatorLineComponent.propTypes = { - dataColumns: PropTypes.object, - entityId: PropTypes.string, - paginationOptions: PropTypes.object, - node: PropTypes.object, - classes: PropTypes.object, - t: PropTypes.func, - fsd: PropTypes.func, - onToggleEntity: PropTypes.func, - selectedElements: PropTypes.object, - deSelectedElements: PropTypes.object, -}; - -const StixDomainObjectIndicatorLineFragment = createFragmentContainer( - StixDomainObjectIndicatorLineComponent, - { - node: graphql` - fragment StixDomainObjectIndicatorLine_node on Indicator { - id - entity_type - name - pattern_type - description - valid_from - valid_until - created - created_at - x_opencti_score - x_opencti_main_observable_type - objectMarking { - id - definition_type - definition - x_opencti_order - x_opencti_color - } - objectLabel { - id - value - color - } - } - `, - }, -); - -export const StixDomainObjectIndicatorLine = compose( - inject18n, - withStyles(styles), -)(StixDomainObjectIndicatorLineFragment); - -class StixDomainObjectIndicatorLineDummyComponent extends Component { - render() { - const { classes, dataColumns } = this.props; - return ( - - - - - - - - -
- -
-
- -
-
- -
-
- -
-
- -
-
- -
- - } - /> - - - -
- ); - } -} - -StixDomainObjectIndicatorLineDummyComponent.propTypes = { - classes: PropTypes.object, - dataColumns: PropTypes.object, -}; - -export const StixDomainObjectIndicatorLineDummy = compose( - inject18n, - withStyles(styles), -)(StixDomainObjectIndicatorLineDummyComponent); diff --git a/opencti-platform/opencti-front/src/private/components/observations/indicators/StixDomainObjectIndicatorsLines.jsx b/opencti-platform/opencti-front/src/private/components/observations/indicators/StixDomainObjectIndicatorsLines.jsx deleted file mode 100644 index 932827e25129..000000000000 --- a/opencti-platform/opencti-front/src/private/components/observations/indicators/StixDomainObjectIndicatorsLines.jsx +++ /dev/null @@ -1,153 +0,0 @@ -import React, { Component } from 'react'; -import * as PropTypes from 'prop-types'; -import { createPaginationContainer, graphql } from 'react-relay'; -import ListLinesContent from '../../../../components/list_lines/ListLinesContent'; -import { StixDomainObjectIndicatorLine, StixDomainObjectIndicatorLineDummy } from './StixDomainObjectIndicatorLine'; -import { setNumberOfElements } from '../../../../utils/Number'; - -const nbOfRowsToLoad = 50; - -class StixDomainObjectIndicatorsLines extends Component { - componentDidUpdate(prevProps) { - setNumberOfElements( - prevProps, - this.props, - 'indicators', - this.props.setNumberOfElements.bind(this), - ); - } - - render() { - const { - initialLoading, - dataColumns, - relay, - entityId, - paginationOptions, - onToggleEntity, - selectedElements, - deSelectedElements, - selectAll, - } = this.props; - return ( - - ); - } -} - -StixDomainObjectIndicatorsLines.propTypes = { - classes: PropTypes.object, - paginationOptions: PropTypes.object, - dataColumns: PropTypes.object.isRequired, - data: PropTypes.object, - relay: PropTypes.object, - stixCoreRelationships: PropTypes.object, - initialLoading: PropTypes.bool, - entityId: PropTypes.string, - setNumberOfElements: PropTypes.func, - onToggleEntity: PropTypes.func, - selectedElements: PropTypes.object, - deSelectedElements: PropTypes.object, - selectAll: PropTypes.bool, -}; - -export const stixDomainObjectIndicatorsLinesQuery = graphql` - query StixDomainObjectIndicatorsLinesQuery( - $search: String - $count: Int! - $cursor: ID - $orderBy: IndicatorsOrdering - $orderMode: OrderingMode - $filters: FilterGroup - ) { - ...StixDomainObjectIndicatorsLines_data - @arguments( - search: $search - count: $count - cursor: $cursor - filters: $filters - orderBy: $orderBy - orderMode: $orderMode - ) - } -`; - -export default createPaginationContainer( - StixDomainObjectIndicatorsLines, - { - data: graphql` - fragment StixDomainObjectIndicatorsLines_data on Query - @argumentDefinitions( - search: { type: "String" } - count: { type: "Int", defaultValue: 25 } - cursor: { type: "ID" } - filters: { type: "FilterGroup" } - orderBy: { type: "IndicatorsOrdering", defaultValue: valid_from } - orderMode: { type: "OrderingMode", defaultValue: desc } - ) { - indicators( - search: $search - first: $count - after: $cursor - orderBy: $orderBy - orderMode: $orderMode - filters: $filters - ) @connection(key: "Pagination_indicators") { - edges { - node { - id - ...StixDomainObjectIndicatorLine_node - } - } - pageInfo { - endCursor - hasNextPage - globalCount - } - } - } - `, - }, - { - direction: 'forward', - getConnectionFromProps(props) { - return props.data && props.data.indicators; - }, - getFragmentVariables(prevVars, totalCount) { - return { - ...prevVars, - count: totalCount, - }; - }, - getVariables(props, { count, cursor }, fragmentVariables) { - return { - count, - cursor, - search: fragmentVariables.search, - orderBy: fragmentVariables.orderBy, - orderMode: fragmentVariables.orderMode, - filters: fragmentVariables.filters, - }; - }, - query: stixDomainObjectIndicatorsLinesQuery, - }, -);