diff --git a/frontend/package.json b/frontend/package.json index a8eec5592..9fc7f3d4b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -41,6 +41,7 @@ "react-scripts": "5.0.0", "react-table": "^7.7.0", "rrule": "^2.6.8", + "uuid": "^8.3.2", "web-vitals": "^1.0.1" }, "scripts": { diff --git a/frontend/src/components/CustomNodesContent/ApiNode/index.jsx b/frontend/src/components/CustomNodesContent/ApiNode/index.jsx index b666ff59f..59ce15957 100644 --- a/frontend/src/components/CustomNodesContent/ApiNode/index.jsx +++ b/frontend/src/components/CustomNodesContent/ApiNode/index.jsx @@ -9,16 +9,16 @@ import customNodeStyle from '../../../utils/customNodeStyle'; import ApiTriggerNodeItem from '../../MoreInfoContent/APITriggerNodeItem'; import MoreInfoMenu from '../../MoreInfoMenu'; -const ApiNode = () => { +const ApiNode = (props) => { // Global state const FlowState = useGlobalFlowState(); const [isEditorPage, setIsEditorPage] = useState(false); - const [isSelected, setIsSelected] = useState(false); + const [, setIsSelected] = useState(false); useEffect(() => { setIsEditorPage(FlowState.isEditorPage.get()); - setIsSelected(FlowState.selectedElementId.get() === 'djdsfjdf4'); + setIsSelected(FlowState.selectedElement.get()?.id === props.id); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -28,13 +28,13 @@ const ApiNode = () => { }, [FlowState.isEditorPage.get()]); useEffect(() => { - setIsSelected(FlowState.selectedElementId.get() === 'djdsfjdf4'); + setIsSelected(FlowState.selectedElement.get()?.id === props.id); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [FlowState.selectedElementId.get()]); + }, [FlowState.selectedElement.get()]); return ( - - + + diff --git a/frontend/src/components/CustomNodesContent/CheckpointNode/index.jsx b/frontend/src/components/CustomNodesContent/CheckpointNode/index.jsx new file mode 100644 index 000000000..dbd65fb37 --- /dev/null +++ b/frontend/src/components/CustomNodesContent/CheckpointNode/index.jsx @@ -0,0 +1,61 @@ +import { faMapMarkedAlt } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Grid, Typography } from '@mui/material'; +import { Box } from '@mui/system'; +import { useEffect, useState } from 'react'; +import { Handle, Position } from 'react-flow-renderer'; +import { useGlobalFlowState } from '../../../pages/Flow'; +import customNodeStyle from '../../../utils/customNodeStyle'; +import PlayTriggerNodeItem from '../../MoreInfoContent/PlayTriggerNodeItem'; +import MoreInfoMenu from '../../MoreInfoMenu'; + +const CheckpointNode = (props) => { + // Global state + const FlowState = useGlobalFlowState(); + + const [isEditorPage, setIsEditorPage] = useState(false); + const [, setIsSelected] = useState(false); + + useEffect(() => { + setIsEditorPage(FlowState.isEditorPage.get()); + setIsSelected(FlowState.selectedElement.get()?.id === props.id); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + setIsEditorPage(FlowState.isEditorPage.get()); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [FlowState.isEditorPage.get()]); + + useEffect(() => { + setIsSelected(FlowState.selectedElement.get()?.id === props.id); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [FlowState.selectedElement.get()]); + + return ( + + + + + + + + Checkpoint + + + + {isEditorPage && ( + + + + + + + + )} + + + ); +}; + +export default CheckpointNode; diff --git a/frontend/src/components/CustomNodesContent/ClearLogsNode/index.jsx b/frontend/src/components/CustomNodesContent/ClearLogsNode/index.jsx index 63d50c1e0..104a5dfa5 100644 --- a/frontend/src/components/CustomNodesContent/ClearLogsNode/index.jsx +++ b/frontend/src/components/CustomNodesContent/ClearLogsNode/index.jsx @@ -4,22 +4,22 @@ import { Grid, Typography } from '@mui/material'; import { Box } from '@mui/system'; import { useEffect } from 'react'; import { useState } from 'react'; -import { Handle } from 'react-flow-renderer'; +import { Handle, Position } from 'react-flow-renderer'; import { useGlobalFlowState } from '../../../pages/Flow'; import ClearLogsEditorModeItem from '../../MoreInfoContent/ClearLogsEditorModeItem'; import ClearLogsNodeItem from '../../MoreInfoContent/ClearLogsNodeItem'; import MoreInfoMenu from '../../MoreInfoMenu'; -const ClearLogsNode = ({ data }) => { +const ClearLogsNode = (props) => { // Global state const FlowState = useGlobalFlowState(); const [isEditorPage, setIsEditorPage] = useState(false); - const [isSelected, setIsSelected] = useState(false); + const [, setIsSelected] = useState(false); useEffect(() => { setIsEditorPage(FlowState.isEditorPage.get()); - setIsSelected(FlowState.selectedElementId.get() === 'djdsfjdf5'); + setIsSelected(FlowState.selectedElement.get()?.id === props.id); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -29,13 +29,14 @@ const ClearLogsNode = ({ data }) => { }, [FlowState.isEditorPage.get()]); useEffect(() => { - setIsSelected(FlowState.selectedElementId.get() === 'djdsfjdf5'); + setIsSelected(FlowState.selectedElement.get()?.id === props.id); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [FlowState.selectedElementId.get()]); + }, [FlowState.selectedElement.get()]); return ( - - + + + @@ -51,12 +52,12 @@ const ClearLogsNode = ({ data }) => { - Python + {props.data.language} - {isEditorPage ? : } + {isEditorPage ? : } diff --git a/frontend/src/components/CustomNodesContent/CustomLine/index.jsx b/frontend/src/components/CustomNodesContent/CustomLine/index.jsx index 31766bdeb..2ef124b5f 100644 --- a/frontend/src/components/CustomNodesContent/CustomLine/index.jsx +++ b/frontend/src/components/CustomNodesContent/CustomLine/index.jsx @@ -1,18 +1,14 @@ import { useTheme } from '@emotion/react'; import React from 'react'; +import { getBezierPath } from 'react-flow-renderer'; -const CustomLine = ({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition, connectionLineType, connectionLineStyle }) => { +const CustomLine = ({ id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, style = {}, data, arrowHeadType, markerEndId }) => { const theme = useTheme(); + const edgePath = getBezierPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition }); return ( - + ); }; diff --git a/frontend/src/components/CustomNodesContent/PlayNode/index.jsx b/frontend/src/components/CustomNodesContent/PlayNode/index.jsx index 797509b43..418016e86 100644 --- a/frontend/src/components/CustomNodesContent/PlayNode/index.jsx +++ b/frontend/src/components/CustomNodesContent/PlayNode/index.jsx @@ -9,16 +9,16 @@ import customNodeStyle from '../../../utils/customNodeStyle'; import PlayTriggerNodeItem from '../../MoreInfoContent/PlayTriggerNodeItem'; import MoreInfoMenu from '../../MoreInfoMenu'; -const PlayNode = () => { +const PlayNode = (props) => { // Global state const FlowState = useGlobalFlowState(); const [isEditorPage, setIsEditorPage] = useState(false); - const [isSelected, setIsSelected] = useState(false); + const [, setIsSelected] = useState(false); useEffect(() => { setIsEditorPage(FlowState.isEditorPage.get()); - setIsSelected(FlowState.selectedElementId.get() === 'djdsfjdf'); + setIsSelected(FlowState.selectedElement.get()?.id === props.id); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -28,13 +28,13 @@ const PlayNode = () => { }, [FlowState.isEditorPage.get()]); useEffect(() => { - setIsSelected(FlowState.selectedElementId.get() === 'djdsfjdf'); + setIsSelected(FlowState.selectedElement.get()?.id === props.id); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [FlowState.selectedElementId.get()]); + }, [FlowState.selectedElement.get()]); return ( - - + + diff --git a/frontend/src/components/CustomNodesContent/ScheduleNode/index.jsx b/frontend/src/components/CustomNodesContent/ScheduleNode/index.jsx index cac4402f4..22c816991 100644 --- a/frontend/src/components/CustomNodesContent/ScheduleNode/index.jsx +++ b/frontend/src/components/CustomNodesContent/ScheduleNode/index.jsx @@ -9,10 +9,10 @@ import customNodeStyle from '../../../utils/customNodeStyle'; import ScheduleTriggerNodeItem from '../../MoreInfoContent/ScheduleTriggerNodeItem'; import MoreInfoMenu from '../../MoreInfoMenu'; -const ScheduleNode = () => { +const ScheduleNode = (props) => { const [isRunning, setIsRunning] = useState(false); const [isEditorPage, setIsEditorPage] = useState(false); - const [isSelected, setIsSelected] = useState(false); + const [, setIsSelected] = useState(false); const FlowState = useGlobalFlowState(); @@ -23,7 +23,7 @@ const ScheduleNode = () => { useEffect(() => { setIsEditorPage(FlowState.isEditorPage.get()); - setIsSelected(FlowState.selectedElementId.get() === 'djdsfjdf3'); + setIsSelected(FlowState.selectedElement.get()?.id === props.id); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -33,13 +33,13 @@ const ScheduleNode = () => { }, [FlowState.isEditorPage.get()]); useEffect(() => { - setIsSelected(FlowState.selectedElementId.get() === 'djdsfjdf3'); + setIsSelected(FlowState.selectedElement.get()?.id === props.id); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [FlowState.selectedElementId.get()]); + }, [FlowState.selectedElement.get()]); return ( - - + + diff --git a/frontend/src/components/DrawerContent/ConfigureLogsDrawer/index.jsx b/frontend/src/components/DrawerContent/ConfigureLogsDrawer/index.jsx index c1cdef474..8c96685b8 100644 --- a/frontend/src/components/DrawerContent/ConfigureLogsDrawer/index.jsx +++ b/frontend/src/components/DrawerContent/ConfigureLogsDrawer/index.jsx @@ -1,11 +1,22 @@ import { faTimes } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Box, Button, Grid, TextField, Typography } from '@mui/material'; +import { useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; +import { useGlobalFlowState } from '../../../pages/Flow'; const ConfigureLogsDrawer = ({ handleClose, refreshData }) => { + const [processorName, setProcessorName] = useState('-'); const { register, handleSubmit } = useForm(); + const FlowState = useGlobalFlowState(); + + useEffect(() => { + setProcessorName(FlowState.selectedElement.get()?.data?.language); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [FlowState.selectedElement.get()]); + async function onSubmit(data) { console.log(data); } @@ -22,7 +33,7 @@ const ConfigureLogsDrawer = ({ handleClose, refreshData }) => { - Processor - Python + Processor - {processorName} { + const [secret, setSecret] = useState(); + const { register, handleSubmit } = useForm(); + + async function onSubmit(data) { + console.log(data); + } + + return ( +
+ + + + + + + + + Trigger - API + + + + Experimental feature + + + + Endpoint: https://localhost:9000/32845-238467-96929 + + + + + Authentication header: + + { + setSecret(newValue); + }} + options={[]} + renderInput={(params) => ( + + )} + /> + + + + + + + + + + Expected + + + + curl .... + + + + +
+ ); +}; + +export default APITRiggerDrawer; diff --git a/frontend/src/components/DrawerContent/EditorDrawers/ChangeProcessorDrawer/index.jsx b/frontend/src/components/DrawerContent/EditorDrawers/ChangeProcessorDrawer/index.jsx index 1b9c40f0d..63f647ed0 100644 --- a/frontend/src/components/DrawerContent/EditorDrawers/ChangeProcessorDrawer/index.jsx +++ b/frontend/src/components/DrawerContent/EditorDrawers/ChangeProcessorDrawer/index.jsx @@ -1,11 +1,24 @@ import { faTimes } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Box, Button, Grid, TextField, Typography } from '@mui/material'; +import { useEffect } from 'react'; +import { useState } from 'react'; import { useForm } from 'react-hook-form'; +import { useGlobalFlowState } from '../../../../pages/Flow'; -const ChangeProcessorDrawer = ({ handleClose, refreshData, processorName }) => { +const ChangeProcessorDrawer = ({ handleClose, refreshData }) => { + const [processorName, setProcessorName] = useState('-'); const { register, handleSubmit } = useForm(); + const FlowState = useGlobalFlowState(); + + useEffect(() => { + console.log(FlowState.selectedElement.get()); + setProcessorName(FlowState.selectedElement.get()?.data?.language); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [FlowState.selectedElement.get()]); + async function onSubmit(data) { console.log(data); } diff --git a/frontend/src/components/EditorSidebar/index.jsx b/frontend/src/components/EditorSidebar/index.jsx index c6364d704..7a5e528d1 100644 --- a/frontend/src/components/EditorSidebar/index.jsx +++ b/frontend/src/components/EditorSidebar/index.jsx @@ -2,6 +2,8 @@ import { faClock } from '@fortawesome/free-regular-svg-icons'; import { faGlobe, faMapMarkedAlt, faPlayCircle, faRunning } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Box, Grid, Typography } from '@mui/material'; +import { useMemo } from 'react'; +import { v4 as uuidv4 } from 'uuid'; const defaultTypographyStyle = { fontSize: 11, @@ -22,15 +24,78 @@ const defaultParentStyle = { borderRadius: '9px', marginTop: 1.2, cursor: 'pointer', + backgroundColor: 'transparent', }; const EditorSidebar = () => { - const onDragStart = (event, nodeType, id) => { - const data = { nodeType, id }; + const onDragStart = (event, nodeType, id, nodeData) => { + const data = { nodeType, id, nodeData }; event.dataTransfer.setData('application/reactflow', JSON.stringify(data)); event.dataTransfer.effectAllowed = 'move'; }; + const inputs = useMemo( + () => [ + { + id: 1, + parent: 'Triggers', + content: [ + { + id: uuidv4(), + icon: faPlayCircle, + text: 'Play', + eventType: 'playNode', + }, + { + id: uuidv4(), + icon: faClock, + text: 'Scheduler', + eventType: 'scheduleNode', + }, + { + id: uuidv4(), + icon: faGlobe, + text: 'API', + eventType: 'apiNode', + }, + ], + }, + { + id: 2, + parent: 'Processors', + content: [ + { + id: uuidv4(), + icon: faRunning, + text: 'Python', + data: { language: 'Python' }, + eventType: 'clearLogsNode', + }, + { + id: uuidv4(), + icon: faRunning, + data: { language: 'Bash' }, + text: 'Bash', + eventType: 'clearLogsNode', + }, + ], + }, + { + id: 3, + parent: 'Checkpoints', + content: [ + { + id: uuidv4(), + icon: faMapMarkedAlt, + text: 'Checkpoint', + eventType: 'checkpointNode', + }, + ], + }, + ], + [] + ); + return ( { }}> {inputs.map((inpt) => ( - <> - + + {inpt.parent} {inpt.content.map((cont) => ( @@ -56,75 +121,18 @@ const EditorSidebar = () => { key={cont.id} container alignItems="center" - sx={defaultParentStyle} + sx={{ ...defaultParentStyle, '&:hover': { backgroundColor: 'sidebar.main' } }} draggable - onDragStart={(event) => onDragStart(event, cont.eventType, cont.id)}> + onDragStart={(event) => onDragStart(event, cont.eventType, cont.id, cont.data)}> {cont.text}
))} - +
))}
); }; -const inputs = [ - { - id: 1, - parent: 'Triggers', - content: [ - { - id: 'djdsfjdf', - icon: faPlayCircle, - text: 'Play trigger', - eventType: 'playNode', - }, - { - id: 'djdsfjdf3', - icon: faClock, - text: 'Scheduler', - eventType: 'scheduleNode', - }, - { - id: 'djdsfjdf4', - icon: faGlobe, - text: 'API trigger', - eventType: 'apiNode', - }, - ], - }, - { - id: 2, - parent: 'Processors', - content: [ - { - id: 'djdsfjdf5', - icon: faRunning, - text: 'Python', - eventType: 'clearLogsNode', - }, - { - id: 'djdsfjdf6', - icon: faRunning, - text: 'Bash', - eventType: 'clearLogsNode', - }, - ], - }, - { - id: 3, - parent: 'Checkpoints', - content: [ - { - id: 1, - icon: faMapMarkedAlt, - text: 'Checkpoint', - eventType: 'input', - }, - ], - }, -]; - export default EditorSidebar; diff --git a/frontend/src/components/MoreInfoContent/APITriggerNodeItem/index.jsx b/frontend/src/components/MoreInfoContent/APITriggerNodeItem/index.jsx index 7b5894ec2..fb82a7d50 100644 --- a/frontend/src/components/MoreInfoContent/APITriggerNodeItem/index.jsx +++ b/frontend/src/components/MoreInfoContent/APITriggerNodeItem/index.jsx @@ -1,11 +1,27 @@ import { MenuItem } from '@mui/material'; +import { useGlobalFlowState } from '../../../pages/Flow'; const ApiTriggerNodeItem = (props) => { + const FlowState = useGlobalFlowState(); + + const handleDeleteElement = () => { + FlowState.triggerDelete.set(FlowState.triggerDelete.get() + 1); + props.handleCloseMenu(); + }; + + const handleOpenAPI = () => { + FlowState.isOpenAPIDrawer.set(true); + props.handleCloseMenu(); + }; + return ( <> - props.handleCloseMenu()}> + API + + Delete + ); }; diff --git a/frontend/src/components/MoreInfoContent/ClearLogsEditorModeItem/index.jsx b/frontend/src/components/MoreInfoContent/ClearLogsEditorModeItem/index.jsx index dea0daf1c..4b94b2108 100644 --- a/frontend/src/components/MoreInfoContent/ClearLogsEditorModeItem/index.jsx +++ b/frontend/src/components/MoreInfoContent/ClearLogsEditorModeItem/index.jsx @@ -15,6 +15,11 @@ const ClearLogsEditorModeItem = (props) => { FlowState.isOpenCommandDrawer.set(true); }; + const handleDeleteElement = () => { + FlowState.triggerDelete.set(FlowState.triggerDelete.get() + 1); + props.handleCloseMenu(); + }; + return ( <> @@ -23,6 +28,9 @@ const ClearLogsEditorModeItem = (props) => { Command + + Delete + ); }; diff --git a/frontend/src/components/MoreInfoContent/PlayTriggerNodeItem/index.jsx b/frontend/src/components/MoreInfoContent/PlayTriggerNodeItem/index.jsx index 54741a19c..97df510ab 100644 --- a/frontend/src/components/MoreInfoContent/PlayTriggerNodeItem/index.jsx +++ b/frontend/src/components/MoreInfoContent/PlayTriggerNodeItem/index.jsx @@ -5,14 +5,14 @@ const PlayTriggerNodeItem = (props) => { const FlowState = useGlobalFlowState(); const handleRemove = () => { + FlowState.triggerDelete.set(FlowState.triggerDelete.get() + 1); props.handleCloseMenu(); - console.log(FlowState.elements.get()); }; return ( <> - Remove + Delete ); diff --git a/frontend/src/components/MoreInfoContent/ScheduleTriggerNodeItem/index.jsx b/frontend/src/components/MoreInfoContent/ScheduleTriggerNodeItem/index.jsx index b10cc6d65..307ad364a 100644 --- a/frontend/src/components/MoreInfoContent/ScheduleTriggerNodeItem/index.jsx +++ b/frontend/src/components/MoreInfoContent/ScheduleTriggerNodeItem/index.jsx @@ -10,11 +10,19 @@ const ScheduleTriggerNodeItem = (props) => { FlowState.isOpenSchedulerDrawer.set(true); }; + const handleDeleteElement = () => { + FlowState.triggerDelete.set(FlowState.triggerDelete.get() + 1); + props.handleCloseMenu(); + }; + return ( <> Scheduler + + Delete + ); }; diff --git a/frontend/src/components/TableContent/PipelineTable/index.jsx b/frontend/src/components/TableContent/PipelineTable/index.jsx index fdfa80365..6ea824801 100644 --- a/frontend/src/components/TableContent/PipelineTable/index.jsx +++ b/frontend/src/components/TableContent/PipelineTable/index.jsx @@ -130,7 +130,7 @@ const PipelineTable = ({ data, filter, setPipelineCount }) => { sx={{ border: 1, borderColor: 'divider', padding: 3, cursor: 'pointer', '&:hover': { background: 'background.hoverSecondary' }, mt: 2 }}> - history.push(`/pipelines/view/${row.original.id}`)}> + history.push(`/pipelines/view/${row.original.pipelineID}`)}> {row.original.name} diff --git a/frontend/src/index.css b/frontend/src/index.css index 78ff360c5..94264ecc6 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -72,3 +72,8 @@ code { .react-flow__node-output.selected { border-color: #0073c6 !important; } + +[draggable='true'] { + position: relative; + z-index: 1; +} diff --git a/frontend/src/pages/Flow.jsx b/frontend/src/pages/Flow.jsx index d984d752b..5dd084291 100644 --- a/frontend/src/pages/Flow.jsx +++ b/frontend/src/pages/Flow.jsx @@ -1,7 +1,7 @@ import { useTheme } from '@emotion/react'; import { Box, Button, Drawer, Grid, Typography } from '@mui/material'; import { useCallback, useEffect, useRef, useState } from 'react'; -import ReactFlow, { addEdge, ControlButton, Controls, getConnectedEdges, isEdge, removeElements } from 'react-flow-renderer'; +import ReactFlow, { addEdge, Controls, getConnectedEdges, isEdge, removeElements } from 'react-flow-renderer'; import { useHistory } from 'react-router-dom'; import ApiNode from '../components/CustomNodesContent/ApiNode'; import ClearLogsNode from '../components/CustomNodesContent/ClearLogsNode'; @@ -11,11 +11,12 @@ import PlayNode from '../components/CustomNodesContent/PlayNode'; import ScheduleNode from '../components/CustomNodesContent/ScheduleNode'; import AddCommandDrawer from '../components/DrawerContent/EditorDrawers/AddCommandDrawer'; import EditorSidebar from '../components/EditorSidebar'; -import { createState, useState as useHookState } from '@hookstate/core'; +import { createState, useState as useHookState, Downgraded } from '@hookstate/core'; import ConfigureLogsDrawer from '../components/DrawerContent/ConfigureLogsDrawer'; import ScheduleDrawer from '../components/DrawerContent/SchedulerDrawer'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faTrash } from '@fortawesome/free-solid-svg-icons'; +import CheckpointNode from '../components/CustomNodesContent/CheckpointNode'; +import { useSnackbar } from 'notistack'; +import APITRiggerDrawer from '../components/DrawerContent/EditorDrawers/APITriggerDrawer'; export const INITIAL_NODE_X_POSITION = 30; export const nodeTypes = { @@ -23,6 +24,7 @@ export const nodeTypes = { playNode: PlayNode, apiNode: ApiNode, clearLogsNode: ClearLogsNode, + checkpointNode: CheckpointNode, }; export const edgeTypes = { custom: CustomEdge, @@ -36,8 +38,9 @@ export const globalFlowState = createState({ isOpenCommandDrawer: false, isOpenAPIDrawer: false, isEditorPage: false, - selectedElementId: null, + selectedElement: null, elements: [], + triggerDelete: 1, }); export const useGlobalFlowState = () => useHookState(globalFlowState); @@ -45,9 +48,10 @@ const Flow = () => { // Hooks const theme = useTheme(); const history = useHistory(); + const { enqueueSnackbar } = useSnackbar(); // Page states - const [isLoadingFlow, setIsLoadingFlow] = useState(true); + const [, setIsLoadingFlow] = useState(true); // Global states const FlowState = useGlobalFlowState(); @@ -60,9 +64,16 @@ const Flow = () => { setOffsetHeight(offsetRef.current.clientHeight); }, [offsetRef]); + useEffect(() => { + if (selectedElement && FlowState.triggerDelete.get() !== 1) { + onClickElementDelete(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [FlowState.triggerDelete.get()]); + // Fetch previous elements useEffect(() => { - const prevElements = FlowState.elements.get(); + const prevElements = FlowState.elements.attach(Downgraded).get(); FlowState.isEditorPage.set(true); setElements([...prevElements]); @@ -79,7 +90,7 @@ const Flow = () => { //Flow methods const onElementsRemove = (elementsToRemove) => setElements((els) => removeElements(elementsToRemove, els)); const onClickElement = useCallback((event, element) => { - FlowState.selectedElementId.set(element.id); + FlowState.selectedElement.set(element); // Set the clicked element in local state setSelectedElement([element]); @@ -93,20 +104,20 @@ const Flow = () => { onElementsRemove([...selectedElement, ...edgesToRemove]); setSelectedElement(null); - FlowState.selectedElementId.set(null); + FlowState.selectedElement.set(null); // eslint-disable-next-line react-hooks/exhaustive-deps }, [elements, selectedElement]); const onLoad = (_reactFlowInstance) => setReactFlowInstance(_reactFlowInstance); const onConnect = (params) => { - setElements((els) => addEdge({ ...params, type: 'custom' }, els)); + setElements((els) => addEdge({ ...params, type: 'custom', arrowHeadType: 'arrowclosed' }, els)); }; const handleSave = useCallback(() => { if (reactFlowInstance) { const flowElements = reactFlowInstance.toObject(); FlowState.elements.set([...flowElements.elements]); FlowState.isEditorPage.set(false); - FlowState.selectedElementId.set(null); + FlowState.selectedElement.set(null); history.goBack(); } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -124,14 +135,21 @@ const Flow = () => { x: event.clientX - reactFlowBounds.left, y: event.clientY - reactFlowBounds.top, }); - const newNode = { - id: type.id, - type: type.nodeType, - position, - data: { label: `${type} node` }, - }; - - setElements((es) => es.concat(newNode)); + + if (elements.filter((el) => el.type === type.nodeType).length > 0 && (type.nodeType === 'playNode' || type.nodeType === 'scheduleNode' || type.nodeType === 'apiNode')) { + enqueueSnackbar('Only one instance of this element is possible.', { variant: 'error' }); + return; + } else { + const newNode = { + id: `${type.id}`, + type: type.nodeType, + position, + data: type.nodeData, + }; + + setElements((es) => es.concat(newNode)); + return; + } }; return ( @@ -168,13 +186,7 @@ const Flow = () => { arrowHeadColor={theme.palette.mode === 'dark' ? '#fff' : '#222'} snapToGrid={true} snapGrid={[15, 15]}> - - {selectedElement && ( - - - - )} - + {elements.length <= 0 ? ( Create a pipeline by dragging the components here. @@ -196,7 +208,7 @@ const Flow = () => { FlowState.isOpenAPIDrawer.set(false)}> - FlowState.isOpenAPIDrawer.set(false)} /> + FlowState.isOpenAPIDrawer.set(false)} /> ); diff --git a/frontend/src/pages/View.jsx b/frontend/src/pages/View.jsx index 214ec1083..efbfe7321 100644 --- a/frontend/src/pages/View.jsx +++ b/frontend/src/pages/View.jsx @@ -4,7 +4,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Box, Button, Drawer, Grid, IconButton, MenuItem, TextField, Typography } from '@mui/material'; import { useEffect, useRef, useState } from 'react'; import ReactFlow, { addEdge, Controls } from 'react-flow-renderer'; -import { useHistory } from 'react-router-dom'; +import { useHistory, useParams } from 'react-router-dom'; import CustomChip from '../components/CustomChip'; import CustomLine from '../components/CustomNodesContent/CustomLine'; import PublishPipelineDrawer from '../components/DrawerContent/PublishPipelineDrawer'; @@ -13,13 +13,17 @@ import MoreInfoMenu from '../components/MoreInfoMenu'; import { edgeTypes, nodeTypes, useGlobalFlowState } from './Flow'; const View = () => { + // Hooks const theme = useTheme(); const history = useHistory(); + // URI parameter + const { pipelineId } = useParams(); + // Page states const [isOpenPublishDrawer, setIsOpenPublishDrawer] = useState(false); const [isRunning, setIsRunning] = useState(false); - const [isLoadingFlow, setIsLoadingFlow] = useState(true); + const [, setIsLoadingFlow] = useState(true); // Global states const FlowState = useGlobalFlowState(); @@ -34,11 +38,9 @@ const View = () => { // Flow states const reactFlowWrapper = useRef(null); - const [reactFlowInstance, setReactFlowInstance] = useState(null); + const [, setReactFlowInstance] = useState(null); const [elements, setElements] = useState([]); - console.log(reactFlowInstance); - // Fetch previous elements useEffect(() => { const prevElements = FlowState.elements.get(); @@ -51,7 +53,7 @@ const View = () => { // Handle edit button const handleGoToEditorPage = () => { FlowState.isEditorPage.set(true); - history.push('/pipelines/flow/123'); + history.push(`/pipelines/flow/${pipelineId}`); }; //Flow methods