From 7440b45ad95d841f496c3a4c4d74921423523b2d Mon Sep 17 00:00:00 2001 From: Bonsai8863 <131906254+Bonsai8863@users.noreply.github.com> Date: Tue, 10 Sep 2024 08:39:25 -0400 Subject: [PATCH] [frontend] DataTable StixCoreRelationshipCreationFromControlledDial --- ...RelationshipCreationFromControlledDial.tsx | 323 ++++++++++-------- 1 file changed, 181 insertions(+), 142 deletions(-) diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_core_relationships/StixCoreRelationshipCreationFromControlledDial.tsx b/opencti-platform/opencti-front/src/private/components/common/stix_core_relationships/StixCoreRelationshipCreationFromControlledDial.tsx index 3e7a0295a7c6b..ba652ec833cc5 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_core_relationships/StixCoreRelationshipCreationFromControlledDial.tsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_core_relationships/StixCoreRelationshipCreationFromControlledDial.tsx @@ -1,38 +1,39 @@ -import React, { FunctionComponent, useContext, useRef, useState } from 'react'; +import React, { FunctionComponent, useContext, useEffect, useState } from "react"; +import { StixCoreRelationshipCreationFromEntityForm, stixCoreRelationshipCreationFromEntityFromMutation, stixCoreRelationshipCreationFromEntityQuery, stixCoreRelationshipCreationFromEntityStixCoreObjectsLineFragment, stixCoreRelationshipCreationFromEntityStixCoreObjectsLinesFragment, stixCoreRelationshipCreationFromEntityStixCoreObjectsLinesQuery, stixCoreRelationshipCreationFromEntityToMutation, TargetEntity } from "./StixCoreRelationshipCreationFromEntity"; +import Drawer from "../drawer/Drawer"; +import { commitMutation, handleErrorInForm, QueryRenderer } from "../../../../relay/environment"; +import { StixCoreRelationshipCreationFromEntityQuery$data } from "./__generated__/StixCoreRelationshipCreationFromEntityQuery.graphql"; +import { Button, CircularProgress, Fab, Typography } from "@mui/material"; +import StixCyberObservableCreation from "../../observations/stix_cyber_observables/StixCyberObservableCreation"; +import StixDomainObjectCreation from "../stix_domain_objects/StixDomainObjectCreation"; +import { Add, ChevronRightOutlined } from "@mui/icons-material"; +import { emptyFilterGroup, useBuildEntityTypeBasedFilterContext, useRemoveIdAndIncorrectKeysFromFilterGroupObject } from "../../../../utils/filters/filtersUtils"; +import { useFormatter } from "../../../../components/i18n"; +import { CreateRelationshipContext } from "../menus/CreateRelationshipContextProvider"; +import { computeTargetStixCyberObservableTypes, computeTargetStixDomainObjectTypes } from "../../../../utils/stixTypeUtils"; +import { FilterGroup } from '../../../../utils/filters/filtersHelpers-types'; import { v4 as uuid } from 'uuid'; -import { useFormatter } from 'src/components/i18n'; -import { Button, CircularProgress, Fab, Typography } from '@mui/material'; -import { Add, ChevronRightOutlined } from '@mui/icons-material'; -import { QueryRenderer, commitMutation, handleErrorInForm } from 'src/relay/environment'; -import { UserContext } from 'src/utils/hooks/useAuth'; -import ListLines from 'src/components/list_lines/ListLines'; -import { emptyFilterGroup, useRemoveIdAndIncorrectKeysFromFilterGroupObject } from 'src/utils/filters/filtersUtils'; -import useFiltersState from 'src/utils/filters/useFiltersState'; -import useEntityToggle from 'src/utils/hooks/useEntityToggle'; -import { resolveRelationsTypes } from 'src/utils/Relation'; -import { ConnectionHandler, RecordSourceSelectorProxy } from 'relay-runtime'; -import { isNodeInConnection } from 'src/utils/store'; -import { FormikConfig } from 'formik'; -import { formatDate } from 'src/utils/Time'; -import { computeTargetStixCyberObservableTypes, computeTargetStixDomainObjectTypes } from 'src/utils/stixTypeUtils'; -import StixCyberObservableCreation from '@components/observations/stix_cyber_observables/StixCyberObservableCreation'; -import StixDomainObjectCreation from '../stix_domain_objects/StixDomainObjectCreation'; -import StixCoreRelationshipCreationForm from './StixCoreRelationshipCreationForm'; -import { CreateRelationshipContext } from '../menus/CreateRelationshipContextProvider'; -import StixCoreRelationshipCreationFromEntityStixCoreObjectsLines, { - stixCoreRelationshipCreationFromEntityStixCoreObjectsLinesQuery, -} from './StixCoreRelationshipCreationFromEntityStixCoreObjectsLines'; -import { StixCoreRelationshipCreationFromEntityStixCoreObjectsLinesQuery$data } from './__generated__/StixCoreRelationshipCreationFromEntityStixCoreObjectsLinesQuery.graphql'; +import { UserContext } from "../../../../utils/hooks/useAuth"; +import useQueryLoading from "../../../../utils/hooks/useQueryLoading"; +import { StixCoreRelationshipCreationFromEntityStixCoreObjectsLinesQuery$variables } from "./__generated__/StixCoreRelationshipCreationFromEntityStixCoreObjectsLinesQuery.graphql"; import { - StixCoreRelationshipCreationFromEntityForm, - TargetEntity, - stixCoreRelationshipCreationFromEntityFromMutation, - stixCoreRelationshipCreationFromEntityQuery, - stixCoreRelationshipCreationFromEntityToMutation, -} from './StixCoreRelationshipCreationFromEntity'; -import { StixCoreRelationshipCreationFromEntityQuery$data } from './__generated__/StixCoreRelationshipCreationFromEntityQuery.graphql'; -import Drawer from '../drawer/Drawer'; -import { FilterGroup } from '../../../../utils/filters/filtersHelpers-types'; + type StixCoreRelationshipCreationFromEntityStixCoreObjectsLinesQuery as StixCoreRelationshipCreationFromEntityStixCoreObjectsLinesQueryType, +} from './__generated__/StixCoreRelationshipCreationFromEntityStixCoreObjectsLinesQuery.graphql'; +import DataTable from "../../../../components/dataGrid/DataTable"; +import { DataTableVariant } from "../../../../components/dataGrid/dataTableTypes"; +import { StixCoreRelationshipCreationFromEntityStixCoreObjectsLines_data$data } from "./__generated__/StixCoreRelationshipCreationFromEntityStixCoreObjectsLines_data.graphql"; +import BulkRelationDialogContainer from "../bulk/dialog/BulkRelationDialogContainer"; +import { UsePreloadedPaginationFragment } from "../../../../utils/hooks/usePreloadedPaginationFragment"; +import { PaginationOptions } from "../../../../components/list_lines"; +import { ModuleHelper } from "../../../../utils/platformModulesHelper"; +import { usePaginationLocalStorage } from "../../../../utils/hooks/useLocalStorage"; +import useEntityToggle from "../../../../utils/hooks/useEntityToggle"; +import StixCoreRelationshipCreationForm from "./StixCoreRelationshipCreationForm"; +import { resolveRelationsTypes } from "../../../../utils/Relation"; +import { ConnectionHandler, RecordSourceSelectorProxy } from "relay-runtime"; +import { isNodeInConnection } from "../../../../utils/store"; +import { FormikConfig } from "formik"; +import { formatDate } from "../../../../utils/Time"; export const CreateRelationshipControlledDial = ({ onOpen }: { onOpen: () => void @@ -157,6 +158,8 @@ export const Header: FunctionComponent = ({ defaultCreatedBy={undefined} defaultMarkingDefinitions={undefined} stixDomainObjectTypes={entityTypes} + onCompleted={undefined} + isFromBulkRelation={false} /> = ({ open={openCreateObservable} handleClose={handleCloseCreateObservable} type={undefined} + isFromBulkRelation={false} + onCompleted={undefined} /> } @@ -197,23 +202,35 @@ export const renderLoader = () => { /** * The first page of the create relationship drawer: selecting the entity/entites - * @param props.id The source entity's id + * @param props.name The source entity's name + * @param props.entity_id The source entity's id + * @param props.entity_type The source entity's type + * @param props.allowedRelationshipTypes * @param props.setTargetEntities Dispatch to set relationship target entities + * @param props.targetEntities * @param props.handleNextStep Function to continue on to the next step * @returns JSX.Element */ const SelectEntity = ({ - id, + name = '', + entity_id, + entity_type, + allowedRelationshipTypes, setTargetEntities, + targetEntities, handleNextStep, }: { - id: string, + name?: string, + entity_id: string, + entity_type: string, + allowedRelationshipTypes?: string[], setTargetEntities: React.Dispatch, + targetEntities: TargetEntity[], handleNextStep: () => void, }) => { const { t_i18n } = useFormatter(); const { state: { stixCoreObjectTypes } } = useContext(CreateRelationshipContext); - const { platformModuleHelpers } = useContext(UserContext); + const typeFilters = (stixCoreObjectTypes ?? []).length > 0 ? { mode: 'and', @@ -227,72 +244,88 @@ const SelectEntity = ({ }], } : emptyFilterGroup; - const [filters, helpers] = useFiltersState(typeFilters, typeFilters); - const virtualEntityTypes = ['Stix-Domain-Object', 'Stix-Cyber-Observable']; - const [sortBy, setSortBy] = useState('_score'); - const [orderAsc, setOrderAsc] = useState(false); - const [numberOfElements, setNumberOfElements] = useState({ - number: 0, - symbol: '', - }); - const [searchTerm, setSearchTerm] = useState(''); - const containerRef = useRef(null); + const virtualEntityTypes = stixCoreObjectTypes ?? ['Stix-Domain-Object', 'Stix-Cyber-Observable']; + const getLocalStorageKey = (entityId: string) => `${entityId}_stixCoreRelationshipCreationFromEntity`; + + const [sortBy, setSortBy] = useState('_score'); + const [orderAsc, setOrderAsc] = useState(false); + + const { viewStorage, helpers } = usePaginationLocalStorage( + getLocalStorageKey(entity_id), + { filters: typeFilters }, + ); + const { searchTerm = '', orderAsc: storageOrderAsc, sortBy: storageSortBy, filters } = viewStorage; + + useEffect(() => { + if (storageSortBy && (storageSortBy !== sortBy)) setSortBy(storageSortBy); + if (storageOrderAsc !== undefined && (storageOrderAsc !== orderAsc)) setOrderAsc(storageOrderAsc); + }, [storageOrderAsc, storageSortBy]); + const { - onToggleEntity, selectedElements, - deSelectedElements, - } = useEntityToggle(`${id}_stixCoreRelationshipCreationFromEntity`); - const onInstanceToggleEntity = (entity: TargetEntity) => { - onToggleEntity(entity); - if (entity.id in (selectedElements || {})) { - const newSelectedElements = { ...selectedElements }; - delete newSelectedElements[entity.id]; - setTargetEntities(Object.values(newSelectedElements)); - } else { - setTargetEntities(Object.values({ - [entity.id]: entity, - ...(selectedElements ?? {}), - })); - } + } = useEntityToggle(getLocalStorageKey(entity_id)); + + useEffect(() => { + const newTargetEntities: TargetEntity[] = Object.values(selectedElements).map((item) => ({ + id: item.id, + entity_type: item.entity_type ?? '', + name: item.name ?? item.observable_value ?? '', + })); + setTargetEntities(newTargetEntities); + }, [selectedElements]); + + const buildColumns = (platformModuleHelpers: ModuleHelper | undefined) => { + const isRuntimeSort = platformModuleHelpers?.isRuntimeFieldEnable(); + return { + entity_type: { + label: 'Type', + percentWidth: 15, + isSortable: true, + }, + value: { + label: 'Value', + percentWidth: 32, + isSortable: false, + }, + createdBy: { + label: 'Author', + percentWidth: 15, + isSortable: isRuntimeSort, + }, + objectLabel: { + label: 'Labels', + percentWidth: 22, + isSortable: false, + }, + objectMarking: { + label: 'Marking', + percentWidth: 15, + isSortable: isRuntimeSort, + }, + }; }; - const searchPaginationOptions = { + const contextFilters = useBuildEntityTypeBasedFilterContext(virtualEntityTypes, filters); + const searchPaginationOptions: PaginationOptions = { search: searchTerm, - filters: useRemoveIdAndIncorrectKeysFromFilterGroupObject(filters, virtualEntityTypes), + filters: contextFilters, orderBy: sortBy, orderMode: orderAsc ? 'asc' : 'desc', - }; - const handleSort = (field: string, sortOrderAsc: boolean) => { - setSortBy(field); - setOrderAsc(sortOrderAsc); - }; - const isRuntimeSort = platformModuleHelpers?.isRuntimeFieldEnable(); - const dataColumns = { - entity_type: { - label: 'Type', - width: '15%', - isSortable: true, - }, - value: { - label: 'Value', - width: '32%', - isSortable: false, - }, - createdBy: { - label: 'Author', - width: '15%', - isSortable: isRuntimeSort, - }, - objectLabel: { - label: 'Labels', - width: '22%', - isSortable: false, - }, - objectMarking: { - label: 'Marking', - width: '15%', - isSortable: isRuntimeSort, - }, - }; + } as PaginationOptions; + const queryRef = useQueryLoading( + stixCoreRelationshipCreationFromEntityStixCoreObjectsLinesQuery, + { ...searchPaginationOptions, count: 100 } as StixCoreRelationshipCreationFromEntityStixCoreObjectsLinesQuery$variables, + ); + + const preloadedPaginationProps = { + linesQuery: stixCoreRelationshipCreationFromEntityStixCoreObjectsLinesQuery, + linesFragment: stixCoreRelationshipCreationFromEntityStixCoreObjectsLinesFragment, + queryRef, + nodePath: ['stixCoreObjects', 'pageInfo', 'globalCount'], + setNumberOfElements: helpers.handleSetNumberOfElements, + } as UsePreloadedPaginationFragment; + + const [tableRootRef, setTableRootRef] = useState(null); + return (
- - ( - - )} - /> - + + {({ platformModuleHelpers }) => ( + <> + {queryRef && ( +
+ data.stixCoreObjects?.edges?.map((n) => n?.node)} + storageKey={getLocalStorageKey(entity_id)} + lineFragment={stixCoreRelationshipCreationFromEntityStixCoreObjectsLineFragment} + initialValues={{}} + toolbarFilters={contextFilters} + preloadedPaginationProps={preloadedPaginationProps} + entityTypes={virtualEntityTypes} + additionalHeaderButtons={[( + + )]} + /> +
+ )} + + )} +
handleNextStep()} - disabled={Object.values(selectedElements).length < 1} + disabled={Object.values(targetEntities).length < 1} style={{ position: 'fixed', bottom: 40, @@ -557,17 +584,29 @@ const StixCoreRelationshipCreationFromControlledDial: FunctionComponent} + containerStyle={{ + minHeight: '100vh', + }} > { if (props?.stixCoreObject) { - return
+ const { name, entity_type, observable_value } = props.stixCoreObject; + return
{step === 0 && ( setStep(1)} /> )}