diff --git a/opencti-platform/opencti-front/src/private/components/settings/sub_types/fintel_templates/FintelTemplate.tsx b/opencti-platform/opencti-front/src/private/components/settings/sub_types/fintel_templates/FintelTemplate.tsx index 2c796140cb003..9d3b00e18957b 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/sub_types/fintel_templates/FintelTemplate.tsx +++ b/opencti-platform/opencti-front/src/private/components/settings/sub_types/fintel_templates/FintelTemplate.tsx @@ -1,8 +1,8 @@ import React, { Suspense } from 'react'; import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; import { useParams } from 'react-router-dom'; -import FintelTemplatePreview from '@components/settings/sub_types/fintel_templates/FintelTemplatePreview'; -import { FintelTemplateProvider } from '@components/settings/sub_types/fintel_templates/FintelTemplateContext'; +import FintelTemplatePreview from './FintelTemplatePreview'; +import { FintelTemplateProvider } from './FintelTemplateContext'; import FintelTemplateContentEditor from './FintelTemplateContentEditor'; import FintelTemplateTabs from './FintelTemplateTabs'; import FintelTemplateHeader from './FintelTemplateHeader'; @@ -25,6 +25,7 @@ export const fintelTemplateQuery = graphql` ...FintelTemplateHeader_template ...FintelTemplateContentEditor_template ...FintelTemplateWidgetsSidebar_template + ...FintelTemplatePreview_template } } `; @@ -55,7 +56,7 @@ const FintelTemplateComponent = ({ queryRef }: FintelTemplateProps) => { diff --git a/opencti-platform/opencti-front/src/private/components/settings/sub_types/fintel_templates/FintelTemplatePreview.tsx b/opencti-platform/opencti-front/src/private/components/settings/sub_types/fintel_templates/FintelTemplatePreview.tsx index 9876a3a8cf94c..4551ba50327ad 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/sub_types/fintel_templates/FintelTemplatePreview.tsx +++ b/opencti-platform/opencti-front/src/private/components/settings/sub_types/fintel_templates/FintelTemplatePreview.tsx @@ -2,25 +2,52 @@ import React, { CSSProperties, useEffect, useState } from 'react'; import { Paper } from '@mui/material'; import { useTheme } from '@mui/styles'; import Typography from '@mui/material/Typography'; -import { useFragment } from 'react-relay'; -import { FintelTemplateQuery$data } from './__generated__/FintelTemplateQuery.graphql'; -import { FintelTemplateWidgetsSidebar_template$key } from './__generated__/FintelTemplateWidgetsSidebar_template.graphql'; +import { graphql, useFragment } from 'react-relay'; import { useFintelTemplateContext } from './FintelTemplateContext'; -import { widgetsFragment } from './FintelTemplateWidgetsSidebar'; import type { Theme } from '../../../../../components/Theme'; import { useFormatter } from '../../../../../components/i18n'; import FintelTemplatePreviewForm, { FintelTemplatePreviewFormInputs } from './FintelTemplatePreviewForm'; import useFileFromTemplate from '../../../../../utils/outcome_template/engine/useFileFromTemplate'; import { htmlToPdfReport } from '../../../../../utils/htmlToPdf/htmlToPdf'; import PdfViewer from '../../../../../components/PdfViewer'; +import { FintelTemplatePreview_template$key } from './__generated__/FintelTemplatePreview_template.graphql'; +import { EngineFintelTemplateQuery$data } from '../../../../../utils/outcome_template/engine/__generated__/EngineFintelTemplateQuery.graphql'; + +const previewFragment = graphql` + fragment FintelTemplatePreview_template on FintelTemplate { + fintel_template_widgets { + variable_name + widget { + id + type + dataSelection { + instance_id + filters + dynamicFrom + dynamicTo + date_attribute + number + attribute + isTo + columns { + label + variableName + attribute + displayStyle + } + } + } + } + } +`; interface FintelTemplatePreviewProps { - fintelTemplate: NonNullable; + data: FintelTemplatePreview_template$key; isTabActive: boolean } const FintelTemplatePreview = ({ - fintelTemplate, + data, isTabActive, }: FintelTemplatePreviewProps) => { const theme = useTheme(); @@ -31,9 +58,9 @@ const FintelTemplatePreview = ({ const [pdf, setPdf] = useState(); const [formValues, setFormValues] = useState(); - const { fintel_template_widgets } = useFragment( - widgetsFragment, - fintelTemplate, + const { fintel_template_widgets } = useFragment( + previewFragment, + data, ); const paperStyle: CSSProperties = { @@ -48,13 +75,14 @@ const FintelTemplatePreview = ({ maxMarkings: string[], fileMarkings: string[], ) => { - const htmlTemplate = await buildFileFromTemplate(scoId, maxMarkings, undefined, { + const template = { template_content: editorValue ?? '', name: 'Preview', id: 'preview', fintel_template_widgets, instance_filters: null, - }); + } as unknown as EngineFintelTemplateQuery$data['fintelTemplate']; + const htmlTemplate = await buildFileFromTemplate(scoId, maxMarkings, undefined, template); const PDF = await htmlToPdfReport(scoName, htmlTemplate, 'Preview', fileMarkings); PDF.getBlob((blob) => { const file = new File([blob], 'Preview.pdf', { type: blob.type }); diff --git a/opencti-platform/opencti-front/src/private/components/settings/sub_types/fintel_templates/FintelTemplateWidgetsList.tsx b/opencti-platform/opencti-front/src/private/components/settings/sub_types/fintel_templates/FintelTemplateWidgetsList.tsx index 7a91d5fb5af9e..60a8210233f1c 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/sub_types/fintel_templates/FintelTemplateWidgetsList.tsx +++ b/opencti-platform/opencti-front/src/private/components/settings/sub_types/fintel_templates/FintelTemplateWidgetsList.tsx @@ -1,15 +1,14 @@ -import List from '@mui/material/List'; -import IconButton from '@mui/material/IconButton'; +import React, { FunctionComponent, MouseEvent, useMemo, useState } from 'react'; import { AddOutlined } from '@mui/icons-material'; -import React, { FunctionComponent, MouseEvent } from 'react'; -import { Tooltip } from '@mui/material'; -import Typography from '@mui/material/Typography'; +import { Menu, MenuItem, Tooltip, IconButton, List, Typography } from '@mui/material'; import { useTheme } from '@mui/styles'; import FintelTemplateWidgetDefault from './FintelTemplateWidgetDefault'; import FintelTemplateWidgetAttribute from './FintelTemplateWidgetAttribute'; import { useFormatter } from '../../../../../components/i18n'; import type { Theme } from '../../../../../components/Theme'; import type { Widget } from '../../../../../utils/widget/widget'; +import { MESSAGING$ } from '../../../../../relay/environment'; +import { useFintelTemplateContext } from './FintelTemplateContext'; export interface FintelTemplateWidget { variable_name: string @@ -18,19 +17,61 @@ export interface FintelTemplateWidget { interface FintelTemplateWidgetsListProps { widgets: FintelTemplateWidget[] - handleOpenPopover: (event: MouseEvent, lineKey: string) => void - title: string - onCreateWidget?: () => void + onCreateWidget: () => void + onDeleteWidget: (w: FintelTemplateWidget) => void + onUpdateWidget: (w: FintelTemplateWidget) => void } const FintelTemplateWidgetsList: FunctionComponent = ({ widgets, - handleOpenPopover, - title, onCreateWidget, + onDeleteWidget, + onUpdateWidget, }) => { - const { t_i18n } = useFormatter(); const theme = useTheme(); + const { t_i18n } = useFormatter(); + const { editorValue } = useFintelTemplateContext(); + + const [menuAnchor, setMenuAnchor] = useState(null); + const [selectedWidget, setSelectedWidget] = useState(); + + const isSelectedWidgetUsed = useMemo(() => { + if (!selectedWidget) return false; + return !!editorValue?.includes(`$${selectedWidget.variable_name}`); + }, [selectedWidget, editorValue]); + + const openPopover = (e: MouseEvent, varName: string) => { + const widget = widgets.find((w) => w.variable_name === varName); + if (widget) { + setSelectedWidget(widget); + setMenuAnchor(e.currentTarget); + } + }; + + const copyWidgetToClipboard = async () => { + if (selectedWidget) { + await navigator.clipboard.writeText(`$${selectedWidget}`); + MESSAGING$.notifySuccess(t_i18n('Widget copied to clipboard')); + } + setMenuAnchor(null); + setSelectedWidget(undefined); + }; + + const deleteWidget = () => { + if (selectedWidget) { + onDeleteWidget(selectedWidget); + } + setMenuAnchor(null); + setSelectedWidget(undefined); + }; + + const updateWidget = () => { + if (selectedWidget) { + onUpdateWidget(selectedWidget); + } + setMenuAnchor(null); + setSelectedWidget(undefined); + }; return ( <> @@ -43,19 +84,20 @@ const FintelTemplateWidgetsList: FunctionComponent - {title} - {onCreateWidget && ( - - - - - - )} + + {t_i18n('Available template widgets')} + + + + + + + {widgets.length === 0 && ( @@ -72,22 +114,41 @@ const FintelTemplateWidgetsList: FunctionComponent ) : ( ); })} + + setMenuAnchor(null)} + > + + {t_i18n('Copy widget to clipboard')} + + + {t_i18n('Update')} + + + {t_i18n('Delete')} + + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/settings/sub_types/fintel_templates/FintelTemplateWidgetsSidebar.tsx b/opencti-platform/opencti-front/src/private/components/settings/sub_types/fintel_templates/FintelTemplateWidgetsSidebar.tsx index 0d6a517fca810..c76d0a2f2b8d0 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/sub_types/fintel_templates/FintelTemplateWidgetsSidebar.tsx +++ b/opencti-platform/opencti-front/src/private/components/settings/sub_types/fintel_templates/FintelTemplateWidgetsSidebar.tsx @@ -1,8 +1,9 @@ -import { Drawer, SxProps, Toolbar, MenuItem, Menu } from '@mui/material'; -import React, { FunctionComponent, MouseEvent, useState } from 'react'; +import { Drawer, SxProps, Toolbar } from '@mui/material'; +import React, { FunctionComponent, useMemo, useState } from 'react'; import { useSettingsMessagesBannerHeight } from '@components/settings/settings_messages/SettingsMessagesBanner'; import { useTheme } from '@mui/styles'; import { graphql, useFragment } from 'react-relay'; +import useFintelTemplateEdit from './useFintelTemplateEdit'; import { FintelTemplateWidgetsSidebar_template$key } from './__generated__/FintelTemplateWidgetsSidebar_template.graphql'; import FintelTemplateWidgetsList, { FintelTemplateWidget } from './FintelTemplateWidgetsList'; import { useFormatter } from '../../../../../components/i18n'; @@ -10,17 +11,15 @@ import type { Theme } from '../../../../../components/Theme'; import { MESSAGING$ } from '../../../../../relay/environment'; import WidgetConfig from '../../../widgets/WidgetConfig'; import type { Widget } from '../../../../../utils/widget/widget'; -import useApiMutation from '../../../../../utils/hooks/useApiMutation'; import { emptyFilterGroup, removeIdFromFilterGroupObject } from '../../../../../utils/filters/filtersUtils'; import DeleteDialog from '../../../../../components/DeleteDialog'; import useDeletion from '../../../../../utils/hooks/useDeletion'; export const FINTEL_TEMPLATE_SIDEBAR_WIDTH = 350; -export const widgetsFragment = graphql` +const sidebarFragment = graphql` fragment FintelTemplateWidgetsSidebar_template on FintelTemplate { id - template_content fintel_template_widgets { variable_name widget { @@ -56,31 +55,11 @@ export const widgetsFragment = graphql` legend distributed } - layout { - w - h - x - y - i - moved - static - } } } } `; -export const fintelTemplateWidgetMutationFieldPatch = graphql` - mutation FintelTemplateWidgetsSidebarWidgetsFieldPatchMutation( - $id: ID! - $input: [EditInput!]! - ) { - fintelTemplateFieldPatch(id: $id, input: $input) { - ...FintelTemplateWidgetsSidebar_template - } - } -`; - interface FintelTemplateWidetsSidebarProps { data: FintelTemplateWidgetsSidebar_template$key, } @@ -89,9 +68,21 @@ const FintelTemplateWidgetsSidebar: FunctionComponent(); const { t_i18n } = useFormatter(); const settingsMessagesBannerHeight = useSettingsMessagesBannerHeight(); - const { id, template_content, fintel_template_widgets: fintelTemplateWidgets } = useFragment(widgetsFragment, data); - const formattedFintelTemplateWidgets: FintelTemplateWidget[] = fintelTemplateWidgets + const { id, fintel_template_widgets } = useFragment(sidebarFragment, data); + + const [commitEditMutation] = useFintelTemplateEdit(); + const deletion = useDeletion({}); + const { handleCloseDelete, handleOpenDelete } = deletion; + + const [isWidgetFormOpen, setIsWidgetFormOpen] = useState(false); + const [selectedWidget, setSelectedWidget] = useState(); + + const selectedWidgetIndex = useMemo(() => { + return fintel_template_widgets.findIndex((w) => w.variable_name === selectedWidget?.variable_name); + }, [fintel_template_widgets, selectedWidget]); + + const formattedFintelTemplateWidgets: FintelTemplateWidget[] = fintel_template_widgets .map((template) => ({ ...template, widget: { @@ -105,86 +96,44 @@ const FintelTemplateWidgetsSidebar: FunctionComponent (template.widget.type === 'attribute' - ? (template.widget.dataSelection[0].columns ?? []).map((c) => ({ - id: template.widget.id, - variableName: c.variableName, - type: template.widget.type, - })) - : { - id: template.widget.id, - variableName: template.variable_name, - type: template.widget.type, - })) - .flat() as { id: string, variableName: string, type: string }[]; - - const usedWidgets = formattedFintelTemplateWidgetsForList.filter((w) => template_content.includes(`$${w.variableName}`)); - - const [selectedVariable, setSelectedVariable] = useState(undefined); - const [isWidgetFormOpen, setIsWidgetFormOpen] = useState(false); - const [anchorEl, setAnchorEl] = useState(null); - - const [commitWidgetUpdate] = useApiMutation(fintelTemplateWidgetMutationFieldPatch); - const deletion = useDeletion({}); - const { handleCloseDelete, handleOpenDelete } = deletion; - - const selectedWidget = formattedFintelTemplateWidgets.find((t) => t.variable_name === selectedVariable)?.widget as Widget ?? undefined; - const selectedWidgetIndex = formattedFintelTemplateWidgets.map((t) => t.variable_name).indexOf(selectedVariable ?? ''); - - const paperStyle: SxProps = { - '.MuiDrawer-paper': { - width: FINTEL_TEMPLATE_SIDEBAR_WIDTH, - padding: `${theme.spacing(2)} 0`, - paddingTop: `calc(${theme.spacing(2)} + ${settingsMessagesBannerHeight}px)`, - }, - }; - - const handleOpenPopover = (event: MouseEvent, variable: string) => { - setAnchorEl(event.currentTarget); - setSelectedVariable(variable); + const onOpenUpdate = (widget: FintelTemplateWidget) => { + setSelectedWidget(widget); + setIsWidgetFormOpen(true); }; - const handleOpenUpdate = () => { - setIsWidgetFormOpen(true); + const onOpenDelete = (widget: FintelTemplateWidget) => { + setSelectedWidget(widget); + handleOpenDelete(); }; const handleWidgetConfigOpen = (isOpen: boolean) => { setIsWidgetFormOpen(isOpen); if (!isOpen) { - setAnchorEl(null); - setSelectedVariable(undefined); + setSelectedWidget(undefined); } }; - const handleClosePopover = () => { - handleWidgetConfigOpen(false); + const closeDeleteConfirm = () => { + handleCloseDelete(); + setSelectedWidget(undefined); }; - const onOpenDelete = () => { - handleOpenDelete(); - setAnchorEl(null); - }; - - const handleDeleteWidget = () => { + const submitDeleteWidget = () => { if (selectedWidgetIndex < 0) { throw Error('Selected widget index should be positive.'); } - const editInput = { - key: 'fintel_template_widgets', - object_path: `fintel_template_widgets/${selectedWidgetIndex}`, - value: [null], - operation: 'remove', - }; - commitWidgetUpdate({ + commitEditMutation({ variables: { id, - input: editInput, - }, - onCompleted: () => { - handleCloseDelete(); - setSelectedVariable(undefined); + input: [{ + key: 'fintel_template_widgets', + object_path: `fintel_template_widgets/${selectedWidgetIndex}`, + value: [null], + operation: 'remove', + }], }, + onError: closeDeleteConfirm, + onCompleted: closeDeleteConfirm, }); }; @@ -208,44 +157,41 @@ const FintelTemplateWidgetsSidebar: FunctionComponent { - setIsWidgetFormOpen(true); - }; - - const copyWidgetToClipboard = async () => { - if (selectedVariable) { - await navigator.clipboard.writeText(`$${selectedVariable}`); - MESSAGING$.notifySuccess(t_i18n('Widget copied to clipboard')); - } - setAnchorEl(null); + const paperStyle: SxProps = { + '.MuiDrawer-paper': { + width: FINTEL_TEMPLATE_SIDEBAR_WIDTH, + padding: `${theme.spacing(2)} 0`, + paddingTop: `calc(${theme.spacing(2)} + ${settingsMessagesBannerHeight}px)`, + }, }; return ( @@ -255,46 +201,27 @@ const FintelTemplateWidgetsSidebar: FunctionComponent setIsWidgetFormOpen(true)} widgets={formattedFintelTemplateWidgets} - handleOpenPopover={handleOpenPopover} + onUpdateWidget={onOpenUpdate} + onDeleteWidget={onOpenDelete} /> - - - {t_i18n('Copy widget to clipboard')} - - - {t_i18n('Update')} - - w.variableName === selectedVariable)} - > - {t_i18n('Delete')} - - - );