diff --git a/apps/hash-frontend/docs/task-dependencies.json b/apps/hash-frontend/docs/task-dependencies.json index 42f583b4118..8ba3fdcf751 100644 --- a/apps/hash-frontend/docs/task-dependencies.json +++ b/apps/hash-frontend/docs/task-dependencies.json @@ -14,6 +14,7 @@ "@hashintel/type-editor", "@local/advanced-types", "@local/eslint", + "@local/flow-petri-net", "@local/hash-graph-client", "@local/hash-graph-sdk", "@local/hash-graph-store", @@ -32,6 +33,7 @@ "@hashintel/petrinaut-core#build", "@local/advanced-types#build", "@local/eslint#build", + "@local/flow-petri-net#build", "@local/hash-graph-client#build", "@local/hash-graph-sdk#build", "@local/hash-graph-store#build", @@ -160,6 +162,7 @@ "@hashintel/petrinaut-core#build", "@local/advanced-types#build", "@local/eslint#build", + "@local/flow-petri-net#build", "@local/hash-graph-client#build", "@local/hash-graph-sdk#build", "@local/hash-graph-store#build", @@ -195,6 +198,7 @@ "@hashintel/petrinaut-core#build", "@local/advanced-types#build", "@local/eslint#build", + "@local/flow-petri-net#build", "@local/hash-graph-client#build", "@local/hash-graph-sdk#build", "@local/hash-graph-store#build", @@ -228,6 +232,7 @@ "@hashintel/petrinaut-core#build", "@local/advanced-types#build", "@local/eslint#build", + "@local/flow-petri-net#build", "@local/hash-graph-client#build", "@local/hash-graph-sdk#build", "@local/hash-graph-store#build", @@ -264,6 +269,7 @@ "@hashintel/petrinaut-core#build", "@local/advanced-types#build", "@local/eslint#build", + "@local/flow-petri-net#build", "@local/hash-graph-client#build", "@local/hash-graph-sdk#build", "@local/hash-graph-store#build", @@ -353,6 +359,7 @@ "@hashintel/petrinaut-core#build", "@local/advanced-types#build", "@local/eslint#build", + "@local/flow-petri-net#build", "@local/hash-graph-client#build", "@local/hash-graph-sdk#build", "@local/hash-graph-store#build", diff --git a/apps/hash-frontend/next.config.js b/apps/hash-frontend/next.config.js index be0e58f093a..33745c90853 100644 --- a/apps/hash-frontend/next.config.js +++ b/apps/hash-frontend/next.config.js @@ -216,6 +216,7 @@ export default withSentryConfig( "zrender", "@hashintel/query-editor", "@local/advanced-types", + "@local/flow-petri-net", "@local/hash-graph-client", "@local/hash-isomorphic-utils", "react-syntax-highlighter", diff --git a/apps/hash-frontend/package.json b/apps/hash-frontend/package.json index 578f631d71d..50d1f89c4a0 100644 --- a/apps/hash-frontend/package.json +++ b/apps/hash-frontend/package.json @@ -51,6 +51,7 @@ "@hashintel/type-editor": "workspace:*", "@lit-labs/react": "1.2.1", "@local/advanced-types": "workspace:*", + "@local/flow-petri-net": "workspace:*", "@local/hash-graph-client": "workspace:*", "@local/hash-graph-sdk": "workspace:*", "@local/hash-graph-store": "workspace:*", @@ -139,7 +140,6 @@ "react-transition-group": "4.4.5", "react-virtuoso": "4.14.1", "react-window": "1.8.11", - "reactflow": "11.11.4", "recharts": "3.8.0", "remark-gfm": "4.0.1", "safe-stable-stringify": "2.5.0", diff --git a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer.tsx b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer.tsx index 8d6bbbcb90f..0682ff1105d 100644 --- a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer.tsx +++ b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer.tsx @@ -1,4 +1,3 @@ -import "reactflow/dist/style.css"; import { useApolloClient, useMutation, useQuery } from "@apollo/client"; import { Box, Collapse, Fade, Stack } from "@mui/material"; import { useRouter } from "next/router"; @@ -8,7 +7,6 @@ import { getRoots } from "@blockprotocol/graph/stdlib"; import { extractEntityUuidFromEntityId } from "@blockprotocol/type-system"; import { IconButton, Skeleton } from "@hashintel/design-system"; import { deserializeSubgraph } from "@local/hash-graph-sdk/subgraph"; -import { actionDefinitions } from "@local/hash-isomorphic-utils/flows/action-definitions"; import { flowDefinitionIds, isGoalFlowDefinitionEntityId, @@ -38,26 +36,18 @@ import { NotFound } from "../../../shared/not-found"; import { useFlowDefinitionAtTime } from "../../../shared/use-flow-definitions"; import { useFlowSchedules } from "../../../shared/use-flow-schedules"; import { ActivityLog } from "./flow-visualizer/activity-log"; -import { DAG } from "./flow-visualizer/dag"; -import { DagSlide } from "./flow-visualizer/dag-slide"; +import { FlowNetSlide } from "./flow-visualizer/flow-net-slide"; +import { FlowNetView } from "./flow-visualizer/flow-net-view"; import { FlowRunSidebar } from "./flow-visualizer/flow-run-sidebar"; +import { getFlattenedSteps } from "./flow-visualizer/get-flattened-steps"; import { Outputs } from "./flow-visualizer/outputs"; import { RunFlowModal } from "./flow-visualizer/run-flow-modal"; import { SectionLabel } from "./flow-visualizer/section-label"; -import { nodeDimensions } from "./flow-visualizer/shared/dimensions"; -import { getNamedInputSources } from "./flow-visualizer/shared/named-input-sources"; import { transitionOptions } from "./flow-visualizer/shared/styles"; -import { - getFlattenedSteps, - groupStepsByDependencyLayer, -} from "./flow-visualizer/sort-graph"; import { Topbar, topbarHeight } from "./flow-visualizer/topbar"; import type { - CustomEdgeType, - CustomNodeType, - EdgeData, - EdgesAndNodes, + FlowStepSummary, LocalProgressLog, LogDisplay, LogThread, @@ -79,87 +69,16 @@ import type { FlowInputValues, } from "@local/hash-isomorphic-utils/flows/types"; -const getGraphFromFlowDefinition = ( +/** Every step, including those inside a `forEach`, for the run sidebar's list of steps. */ +const getStepSummaries = ( flowDefinition: FlowDefinitionType, - showAllDependencies = false, -) => { - const { layerByStepId } = groupStepsByDependencyLayer(flowDefinition.steps); - - const flattenedSteps = getFlattenedSteps(flowDefinition.steps); - - const derivedNodes: CustomNodeType[] = flattenedSteps.map((step) => { - const node: CustomNodeType = { - id: step.stepId, - data: { - kind: step.kind, - actionDefinition: - step.kind === "action" - ? actionDefinitions[step.actionDefinitionId] - : null, - label: step.description, - inputSources: getNamedInputSources(step), - }, - type: step.kind, - parentNode: step.parallelParentId, - extent: step.parallelParentId ? ("parent" as const) : undefined, - position: { x: 0, y: 0 }, - ...nodeDimensions, - }; - - return node; - }); - - const derivedEdges: CustomEdgeType[] = []; - for (let i = 0; i < derivedNodes.length; i++) { - const node = derivedNodes[i]!; - - const baseEdgeOptions = { - data: { sourceStatus: "Waiting" } satisfies EdgeData, - type: "custom-edge", - /** - * If this isn't set, the edge container will have a zIndex of 0 and appear below a parent node, - * meaning that edges between nodes in a sub-flow are not visible. - * Needs further investigation with more complex flows. - */ - zIndex: 1, - }; - - if (showAllDependencies) { - for (const inputSource of node.data.inputSources) { - if (inputSource.kind === "step-output") { - derivedEdges.push({ - id: `${flowDefinition.name}-${inputSource.stepId}-${node.id}`, - source: inputSource.stepId, - sourceHandle: inputSource.outputName, - target: node.id, - targetHandle: inputSource.inputName, - ...baseEdgeOptions, - }); - } - } - } else { - const nextNode = derivedNodes[i + 1]; - - if ( - nextNode && - nextNode.parentNode !== node.id && - layerByStepId.get(node.id) !== layerByStepId.get(nextNode.id) - ) { - derivedEdges.push({ - id: `${node.id}-${nextNode.id}`, - source: node.id, - target: nextNode.id, - ...baseEdgeOptions, - }); - } - } - } - - return { - nodes: derivedNodes, - edges: derivedEdges, - }; -}; +): FlowStepSummary[] => + getFlattenedSteps(flowDefinition.steps).map((step) => ({ + stepId: step.stepId, + parentStepId: step.parallelParentId, + kind: step.kind, + label: step.description, + })); const logHeight = 400; @@ -187,7 +106,7 @@ export const FlowRunVisualizerSkeleton = () => ( ); export const FlowVisualizer = () => { - const [showDag, setShowDag] = useState(false); + const [showFlowNet, setShowFlowNet] = useState(false); const [bottomPanelIsCollapsed, setBottomPanelIsCollapsed] = useState(false); @@ -255,20 +174,14 @@ export const FlowVisualizer = () => { const selectedFlowDefinition = selectedFlow?.flowDefinition; - const { nodes: derivedNodes, edges: derivedEdges } = useMemo(() => { - if (!selectedFlowDefinition) { - return { nodes: [], edges: [] }; - } - return getGraphFromFlowDefinition(selectedFlowDefinition); - }, [selectedFlowDefinition]); + const steps = useMemo( + () => + selectedFlowDefinition ? getStepSummaries(selectedFlowDefinition) : [], + [selectedFlowDefinition], + ); const [showRunModal, setShowRunModal] = useState(false); - const graph = useMemo( - () => ({ nodes: derivedNodes, edges: derivedEdges }), - [derivedNodes, derivedEdges], - ); - const { logs, persistedEntitiesMetadata, @@ -684,10 +597,9 @@ export const FlowVisualizer = () => { return ( <> {selectedFlowRun && ( - setShowDag(false)} + setShowFlowNet(false)} selectedFlowDefinition={selectedFlowDefinition} flowDefinitionEntityId={selectedFlow.flowDefinitionEntityId} /> @@ -720,7 +632,7 @@ export const FlowVisualizer = () => { direction="row" sx={{ flex: 1, - minHeight: `calc(100% - ${logHeight + topbarHeight}px)`, + minHeight: `calc(100% - ${(selectedFlowRun ? logHeight : 0) + topbarHeight}px)`, overflow: "auto", width: "100%", background: ({ palette }) => @@ -735,9 +647,9 @@ export const FlowVisualizer = () => { flowDefinitionEntityId={selectedFlow.flowDefinitionEntityId} flowRunEntityId={selectedFlowRun.flowRunEntityId} flowScheduleId={selectedFlowRun.flowScheduleId ?? null} - graph={graph} + steps={steps} name={selectedFlowRun.name} - showDag={() => setShowDag(true)} + showFlowNet={() => setShowFlowNet(true)} /> ) : null} { relevantEntityIds={relevantEntityIds} /> ) : ( - + + + )} - - ({ - background: bottomPanelIsCollapsed - ? palette.common.white - : palette.gray[10], - borderTop: `2px solid ${palette.gray[20]}`, - height: logHeight, - maxHeight: "100%", - maxWidth: "100%", - px: 3, - transition: transitions.create("background", transitionOptions), - width: "100%", - })} + {selectedFlowRun ? ( + - - `1px solid ${palette.gray[20]}`, - height: "100%", - py: 2.5, - pr: 3, - width: "70%", - }} - > - - - - - ({ - transform: `rotate(${bottomPanelIsCollapsed ? "90deg" : "270deg"})`, - - "&:hover": { - backgroundColor: palette.gray[20], - color: palette.gray[60], - }, - })} - onClick={() => - setBottomPanelIsCollapsed(!bottomPanelIsCollapsed) - } - > - - - - - + ({ + background: bottomPanelIsCollapsed + ? palette.common.white + : palette.gray[10], + borderTop: `2px solid ${palette.gray[20]}`, + height: logHeight, + maxHeight: "100%", + maxWidth: "100%", + px: 3, + transition: transitions.create("background", transitionOptions), + width: "100%", + })} + > + + + `1px solid ${palette.gray[20]}`, + height: "100%", + py: 2.5, + pr: 3, + width: "70%", + }} + > + + + + + ({ + transform: `rotate(${bottomPanelIsCollapsed ? "90deg" : "270deg"})`, + + "&:hover": { + backgroundColor: palette.gray[20], + color: palette.gray[60], + }, + })} + onClick={() => + setBottomPanelIsCollapsed(!bottomPanelIsCollapsed) + } + > + + + + + + ) : null} ); diff --git a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/dag.tsx b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/dag.tsx deleted file mode 100644 index a028c72a7ad..00000000000 --- a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/dag.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { Stack, Typography } from "@mui/material"; -import { format } from "date-fns"; -import { ReactFlowProvider } from "reactflow"; - -import { useFlowRunsContext } from "../../../../shared/flow-runs-context"; -import { flowSectionBorderRadius, transitionOptions } from "./shared/styles"; -import { Swimlane } from "./swimlane"; - -import type { EdgesAndNodes } from "./shared/types"; -import type { - FlowActionDefinitionId, - FlowDefinition, -} from "@local/hash-isomorphic-utils/flows/types"; - -export const DAG = ({ - graph, - selectedFlowDefinition, -}: { - graph: EdgesAndNodes; - selectedFlowDefinition: FlowDefinition; -}) => { - const { selectedFlowRun } = useFlowRunsContext(); - - return ( - ({ - background: palette.common.white, - border: `1px solid ${palette.gray[selectedFlowRun ? 20 : 30]}`, - borderRadius: flowSectionBorderRadius, - "& > :first-of-type": { - borderTopRightRadius: flowSectionBorderRadius, - borderTopLeftRadius: flowSectionBorderRadius, - }, - "& > :last-child": { - borderBottomRightRadius: flowSectionBorderRadius, - borderBottomLeftRadius: flowSectionBorderRadius, - }, - "& > :last-child > :first-of-type": { - borderBottomLeftRadius: flowSectionBorderRadius, - }, - flexWrap: "wrap", - transition: transitions.create("border", transitionOptions), - width: "fit-content", - })} - > - `1px solid ${palette.gray[20]}`, - p: 3, - }} - > - {selectedFlowRun ? ( - palette.gray[60] }} - > - Started{" "} - - {selectedFlowRun.flowScheduleId ? "by a schedule" : "manually"} - {" "} - at{" "} - {format( - new Date(selectedFlowRun.startedAt), - "yyyy-MM-dd 'at' h:mm a", - )} - - ) : ( - <> - - {selectedFlowDefinition.name} - - - {selectedFlowDefinition.description} - - - )} - - - - - - ); -}; diff --git a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/dag-slide.tsx b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/flow-net-slide.tsx similarity index 82% rename from apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/dag-slide.tsx rename to apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/flow-net-slide.tsx index 406528017e2..50d5c7cf916 100644 --- a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/dag-slide.tsx +++ b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/flow-net-slide.tsx @@ -3,31 +3,28 @@ import { Backdrop, Box, Slide, Stack } from "@mui/material"; import { IconButton, XMarkRegularIcon } from "@hashintel/design-system"; import { isGoalFlowDefinitionEntityId } from "@local/hash-isomorphic-utils/flows/flow-definition-ids"; -import { DAG } from "./dag"; +import { FlowNetView } from "./flow-net-view"; import { Topbar } from "./topbar"; -import type { EdgesAndNodes } from "./shared/types"; import type { EntityId } from "@blockprotocol/type-system"; import type { FlowActionDefinitionId, FlowDefinition, } from "@local/hash-isomorphic-utils/flows/types"; -type DagSlideProps = { - graph: EdgesAndNodes; +type FlowNetSlideProps = { open: boolean; onClose: () => void; selectedFlowDefinition: FlowDefinition; flowDefinitionEntityId: EntityId; }; -export const DagSlide = ({ - graph, +export const FlowNetSlide = ({ open, onClose, selectedFlowDefinition, flowDefinitionEntityId, -}: DagSlideProps) => { +}: FlowNetSlideProps) => { const isGoal = isGoalFlowDefinitionEntityId(flowDefinitionEntityId); return ( @@ -46,10 +43,11 @@ export const DagSlide = ({ > palette.gray[10], - maxWidth: { xs: "90%", md: 800, lg: 1100 }, + width: "90vw", }} > - - + + diff --git a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/flow-net-view.tsx b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/flow-net-view.tsx new file mode 100644 index 00000000000..d2f680c59f4 --- /dev/null +++ b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/flow-net-view.tsx @@ -0,0 +1,148 @@ +import { Box, Typography } from "@mui/material"; +import dynamic from "next/dynamic"; +import { useEffect, useState } from "react"; + +import { compileFlowDefinition } from "@local/flow-petri-net/compile-flow-definition"; + +import { flowSectionBorderRadius } from "./shared/styles"; + +import type { SDCPN } from "@hashintel/petrinaut"; +import type { + FlowActionDefinitionId, + FlowDefinition, +} from "@local/hash-isomorphic-utils/flows/types"; +import type { ReactNode } from "react"; + +const Message = ({ children }: { children: ReactNode }) => ( + + palette.gray[60] }} + > + {children} + + +); + +const PetrinautPreview = dynamic( + () => + import("./flow-net-view/petrinaut-preview").then( + (mod) => mod.PetrinautPreview, + ), + { ssr: false, loading: () => Loading Petrinaut… }, +); + +/** + * Nets compiled so far, by the definition they were compiled from. A definition is loaded once per edition, so + * switching between the step graph and the net doesn't compile it again. + */ +const compiledNets = new WeakMap< + FlowDefinition, + Promise +>(); + +const compileFlowNet = ( + flowDefinition: FlowDefinition, +) => { + let compiledNet = compiledNets.get(flowDefinition); + + if (!compiledNet) { + compiledNet = compileFlowDefinition(flowDefinition); + compiledNets.set(flowDefinition, compiledNet); + } + + return compiledNet; +}; + +type CompiledNet = + | { status: "compiling" } + | { status: "compiled"; net: SDCPN } + | { status: "failed"; message: string }; + +/** + * The net compiled from a flow definition. Compiling lays the net out, so it finishes after the first render. + */ +const useCompiledFlowNet = ( + flowDefinition: FlowDefinition, +): CompiledNet => { + const [compiled, setCompiled] = useState<{ + flowDefinition: FlowDefinition; + result: CompiledNet; + } | null>(null); + + useEffect(() => { + let cancelled = false; + + compileFlowNet(flowDefinition).then( + (net) => { + if (!cancelled) { + setCompiled({ flowDefinition, result: { status: "compiled", net } }); + } + }, + (error: unknown) => { + if (!cancelled) { + setCompiled({ + flowDefinition, + result: { + status: "failed", + message: error instanceof Error ? error.message : String(error), + }, + }); + } + }, + ); + + return () => { + cancelled = true; + }; + }, [flowDefinition]); + + return compiled?.flowDefinition === flowDefinition + ? compiled.result + : { status: "compiling" }; +}; + +/** + * A flow as its compiled Petri net, read-only. It fills the space it's given. + */ +export const FlowNetView = ({ + flowDefinition, +}: { + flowDefinition: FlowDefinition; +}) => { + const compiledNet = useCompiledFlowNet(flowDefinition); + + return ( + palette.common.white, + border: ({ palette }) => `1px solid ${palette.gray[30]}`, + borderRadius: flowSectionBorderRadius, + height: "100%", + overflow: "hidden", + width: "100%", + }} + > + {compiledNet.status === "compiled" ? ( + + ) : compiledNet.status === "failed" ? ( + + This flow can't be compiled to a Petri net: {compiledNet.message} + + ) : ( + Compiling the flow… + )} + + ); +}; diff --git a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/flow-net-view/petrinaut-preview.tsx b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/flow-net-view/petrinaut-preview.tsx new file mode 100644 index 00000000000..318adb280e5 --- /dev/null +++ b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/flow-net-view/petrinaut-preview.tsx @@ -0,0 +1,6 @@ +/* + * Loaded on demand by the net view, so Petrinaut's components and styles only reach a page that shows a net. + */ +import "@hashintel/petrinaut/dist/main.css"; + +export { PetrinautPreview } from "@hashintel/petrinaut/preview"; diff --git a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/flow-run-sidebar.tsx b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/flow-run-sidebar.tsx index de8aa2cfaa3..b907567f93e 100644 --- a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/flow-run-sidebar.tsx +++ b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/flow-run-sidebar.tsx @@ -18,7 +18,7 @@ import { SectionLabel } from "./section-label"; import { flowSectionBorderRadius } from "./shared/styles"; import type { FlowRun } from "../../../../../graphql/api-types.gen"; -import type { EdgesAndNodes } from "./shared/types"; +import type { FlowStepSummary } from "./shared/types"; import type { EntityId, EntityUuid } from "@blockprotocol/type-system"; import type { GoalFlowInputName } from "@local/hash-isomorphic-utils/flows/definitions/goal-flow-definitions"; import type { PropsWithChildren } from "react"; @@ -41,18 +41,18 @@ type FlowRunSidebarProps = { flowDefinitionEntityId: EntityId; flowRunEntityId: EntityId; flowScheduleId: EntityUuid | null; - graph: EdgesAndNodes; + steps: FlowStepSummary[]; name: FlowRun["name"]; - showDag: () => void; + showFlowNet: () => void; }; export const FlowRunSidebar = ({ flowDefinitionEntityId, flowRunEntityId, flowScheduleId, - graph, + steps, name, - showDag, + showFlowNet, }: FlowRunSidebarProps) => { const { selectedFlowRun } = useFlowRunsContext(); @@ -176,7 +176,7 @@ export const FlowRunSidebar = ({ showDag()} + onClick={() => showFlowNet()} sx={{ px: 0.3, py: 0, @@ -201,7 +201,7 @@ export const FlowRunSidebar = ({ - + diff --git a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/flow-run-sidebar/group-status.tsx b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/flow-run-sidebar/group-status.tsx index f8b77c3b555..9932eca116f 100644 --- a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/flow-run-sidebar/group-status.tsx +++ b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/flow-run-sidebar/group-status.tsx @@ -1,5 +1,5 @@ import { Box, Collapse, Stack, Typography } from "@mui/material"; -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useState } from "react"; import { CaretDownSolidIcon } from "@hashintel/design-system"; @@ -13,24 +13,14 @@ import { WaitingIcon, } from "./group-status/group-step-status"; -import type { EdgesAndNodes } from "../shared/types"; - -export const GroupStatus = ({ graph }: { graph: EdgesAndNodes }) => { - const groupStepsWithIds = useMemo( - () => - graph.nodes.map((node) => ({ - ...node, - stepId: node.id, - parentStepId: node.parentNode, - })), - [graph], - ); +import type { FlowStepSummary } from "../shared/types"; +export const GroupStatus = ({ steps }: { steps: FlowStepSummary[] }) => { const { closedAt, scheduledAt, overallStatus: groupStatus, - } = useStatusForSteps(groupStepsWithIds) ?? {}; + } = useStatusForSteps(steps) ?? {}; const [showSteps, setShowSteps] = useState(groupStatus === "In Progress"); @@ -130,11 +120,11 @@ export const GroupStatus = ({ graph }: { graph: EdgesAndNodes }) => { my: 1, }} > - {groupStepsWithIds.map((step) => ( + {steps.map((step) => ( ))} diff --git a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/get-flattened-steps.ts b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/get-flattened-steps.ts new file mode 100644 index 00000000000..33485e8cbe9 --- /dev/null +++ b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/get-flattened-steps.ts @@ -0,0 +1,23 @@ +import type { StepDefinition } from "@local/hash-isomorphic-utils/flows/types"; + +type StepWithParallelParentId = StepDefinition & { parallelParentId?: string }; + +export const getFlattenedSteps = ( + steps: StepDefinition[], + parallelParentId?: string, +): StepWithParallelParentId[] => { + const flattenedSteps: StepWithParallelParentId[] = []; + + for (const step of steps) { + flattenedSteps.push({ + ...step, + parallelParentId, + }); + + if (step.kind === "for-each") { + flattenedSteps.push(...getFlattenedSteps(step.steps, step.stepId)); + } + } + + return flattenedSteps; +}; diff --git a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/marker-end.tsx b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/marker-end.tsx deleted file mode 100644 index 34bbb558563..00000000000 --- a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/marker-end.tsx +++ /dev/null @@ -1,30 +0,0 @@ -type MarkerEndProps = { - id: string; - color: string; -}; - -export const MarkerEnd = ({ id, color }: MarkerEndProps) => { - return ( - - - - - - ); -}; diff --git a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/shared/dimensions.ts b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/shared/dimensions.ts deleted file mode 100644 index 3991331ab7a..00000000000 --- a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/shared/dimensions.ts +++ /dev/null @@ -1,18 +0,0 @@ -export const nodeDimensions = { - width: 350, - height: 225, -}; - -const tabHeight = 40; -const offset = 12; - -export const nodeTabHeight = { - gross: tabHeight, - offset, - net: tabHeight - offset, -}; - -export const parentGroupPadding = { - base: 30, - top: 85, -}; diff --git a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/shared/named-input-sources.ts b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/shared/named-input-sources.ts deleted file mode 100644 index 8870f516220..00000000000 --- a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/shared/named-input-sources.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { - StepDefinition, - StepInputSource, -} from "@local/hash-isomorphic-utils/flows/types"; - -/** - * A step input's source, with the name of the input it feeds. - */ -export type NamedStepInputSource = StepInputSource & { inputName: string }; - -/** - * The sources of a step's inputs, as a list: an action step's connected inputs, or a for-each step's array - * (as an input named `over`). - */ -export const getNamedInputSources = ( - step: StepDefinition, -): NamedStepInputSource[] => - step.kind === "action" - ? Object.entries(step.inputs).map(([inputName, source]) => ({ - ...source, - inputName, - })) - : [{ ...step.over, inputName: "over" }]; diff --git a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/shared/types.ts b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/shared/types.ts index b0cf5babc29..ce7fbd2e4d8 100644 --- a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/shared/types.ts +++ b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/shared/types.ts @@ -1,31 +1,17 @@ -import type { SimpleStatus } from "../../../../../shared/flow-runs-context"; -import type { NamedStepInputSource } from "./named-input-sources"; import type { - ActionDefinition, - FlowActionDefinitionId, ProgressLogBase, ProposedEntity, StepDefinition, StepProgressLog, } from "@local/hash-isomorphic-utils/flows/types"; -import type { Edge, Node } from "reactflow"; -export type NodeData = { +/** A step of a flow, including one inside a `forEach`, for the run sidebar's list of steps. */ +export type FlowStepSummary = { + stepId: string; + /** The `forEach` step it's inside, if any. */ + parentStepId?: string; kind: StepDefinition["kind"]; - actionDefinition?: ActionDefinition | null; label: string; - inputSources: NamedStepInputSource[]; -}; - -export type CustomNodeType = Node; - -export type EdgeData = { sourceStatus: SimpleStatus }; - -export type CustomEdgeType = Edge; - -export type EdgesAndNodes = { - edges: CustomEdgeType[]; - nodes: CustomNodeType[]; }; export type StateChangeLog = ProgressLogBase & { diff --git a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/sort-graph.ts b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/sort-graph.ts deleted file mode 100644 index fc371bfcf72..00000000000 --- a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/sort-graph.ts +++ /dev/null @@ -1,248 +0,0 @@ -import { getNamedInputSources } from "./shared/named-input-sources"; - -import type { StepDefinition } from "@local/hash-isomorphic-utils/flows/types"; - -type StepWithParallelParentId = StepDefinition & { parallelParentId?: string }; - -export const getFlattenedSteps = ( - steps: StepDefinition[], - parallelParentId?: string, -): StepWithParallelParentId[] => { - const flattenedSteps: StepWithParallelParentId[] = []; - - for (const step of steps) { - flattenedSteps.push({ - ...step, - parallelParentId, - }); - - if (step.kind === "for-each") { - flattenedSteps.push(...getFlattenedSteps(step.steps, step.stepId)); - } - } - - return flattenedSteps; -}; - -export const sortStepsTopologically = ( - possiblyNestedSteps: StepDefinition[], -): StepWithParallelParentId[] => { - const sortedItems: StepDefinition[] = []; - - const stepsWithoutDependencies: StepDefinition[] = []; - - const dependencyCountByStepId: Map = new Map(); - - const steps = getFlattenedSteps(possiblyNestedSteps); - - for (const step of steps) { - const dependencyCount = getNamedInputSources(step).filter( - (input) => input.kind !== "constant" && input.kind !== "flow-input", - ).length; - - dependencyCountByStepId.set(step.stepId, dependencyCount); - - if (dependencyCount === 0) { - stepsWithoutDependencies.push(step); - } - } - - while (stepsWithoutDependencies.length > 0) { - const readyStep = stepsWithoutDependencies.shift()!; - - sortedItems.push(readyStep); - - const stepsReducedToZeroDependenciesByReadyStep: string[] = []; - - for (const possiblyDependentStep of steps) { - const numberOfDependenciesSatisfiedByReadyStep = getNamedInputSources( - possiblyDependentStep, - ).filter((input) => { - if (input.kind === "item") { - /** - * An item is satisfied by the readyStep if the readyStep is the for-each step it is nested in - */ - return ( - readyStep.kind === "for-each" && - possiblyDependentStep.parallelParentId === readyStep.stepId - ); - } - - if (input.kind === "constant" || input.kind === "flow-input") { - /** We excluded these from the dependency count when calculating it */ - return false; - } - - const sourceStep = steps.find((step) => step.stepId === input.stepId); - - if (!sourceStep) { - throw new Error( - `Could not find source step with stepId ${input.stepId}`, - ); - } - - if (sourceStep.kind === "for-each") { - /** - * If the source step is a for-each step, its outputs are only available once all of its children - * have no dependencies left. This may be satisfied when any one of it or its children are processed. - */ - return [sourceStep, ...sourceStep.steps].every( - (step) => - /** - * The final dependency of steps within a for-each step may have been reduced to zero in this iteration, - * in which case we don't want to count a step dependent on the for-each step as being satisfied yet. - * Any steps reduced to zero in this iteration will be processed in a future iteration, - * at which point the step dependent on the for-each step can be pushed into the zero deps queue. - */ - !stepsReducedToZeroDependenciesByReadyStep.includes( - step.stepId, - ) && dependencyCountByStepId.get(step.stepId) === 0, - ); - } - - return input.stepId === readyStep.stepId; - }).length; - - if (numberOfDependenciesSatisfiedByReadyStep) { - const currentCount = dependencyCountByStepId.get( - possiblyDependentStep.stepId, - ); - - if (currentCount === undefined) { - throw new Error( - `Dependency count for step with stepId ${possiblyDependentStep.stepId} is undefined`, - ); - } - - const newCount = - currentCount - numberOfDependenciesSatisfiedByReadyStep; - dependencyCountByStepId.set(possiblyDependentStep.stepId, newCount); - - if (newCount === 0) { - stepsWithoutDependencies.push(possiblyDependentStep); - stepsReducedToZeroDependenciesByReadyStep.push( - possiblyDependentStep.stepId, - ); - } - } - } - } - - const stepsWithCycles: string[] = []; - for (const [stepId, count] of dependencyCountByStepId.entries()) { - if (count > 0) { - stepsWithCycles.push(stepId); - } - } - - if (stepsWithCycles.length > 0) { - throw new Error( - `Cycles detected in steps with stepIds: ${stepsWithCycles.join(", ")}`, - ); - } - - return sortedItems; -}; - -/** - * A topologically ordered list of groups of steps ('layers'), where each layer can execute once the previous is complete. - */ -type DependencyLayers = StepDefinition[][]; - -/** - * A map of stepId to the index of the layer it is in. - */ -type DependencyLayerByStepId = Map; - -export const groupStepsByDependencyLayer = ( - untransformedSteps: StepDefinition[], -): { layers: DependencyLayers; layerByStepId: DependencyLayerByStepId } => { - const layers: DependencyLayers = []; - const layerByStepId: DependencyLayerByStepId = new Map(); - - const sortedAndFlattenedSteps = sortStepsTopologically(untransformedSteps); - - for (const step of sortedAndFlattenedSteps) { - let stepPlacedInLayer = false; - - for (let index = 0; index < layers.length; index++) { - const layer = layers[index]!; - - const dependenciesAreInEarlierGroup = getNamedInputSources(step).every( - (input) => { - if (input.kind === "constant" || input.kind === "flow-input") { - return true; - } - - const inputSourceStepId = - input.kind === "step-output" - ? input.stepId - : sortedAndFlattenedSteps.find( - (possibleParent) => - possibleParent.kind === "for-each" && - possibleParent.stepId === step.parallelParentId, - )?.stepId; - - if (!inputSourceStepId) { - throw new Error( - `Could not find input source step for input '${input.inputName}' for step with stepId '${step.stepId}'`, - ); - } - - const inputStep = sortedAndFlattenedSteps.find( - (stp) => stp.stepId === inputSourceStepId, - ); - if (!inputStep) { - throw new Error( - `Could not find input source step with stepId ${inputSourceStepId}`, - ); - } - - const inputStepIds = - inputStep.kind === "for-each" - ? /** - * If the input is from a for-each step, all steps in it must be satisfied for this input to be - * considered satisfied. - * - * Technically this input is satisfied as soon as the nested step which produces the collected output - * is complete, and there may be leaf steps within the for-each step that do not contribute to that - * output, but that would make for a messy DAG visualization. - */ - [inputStep.stepId, ...inputStep.steps.map((stp) => stp.stepId)] - : [inputStep.stepId]; - - const inputIsSatisfied = inputStepIds.every((inputStepId) => { - const inputGroupIndex = layerByStepId.get(inputStepId); - - if (inputGroupIndex === undefined) { - throw new Error( - `Source step with stepId ${inputStepId} does not appear before step with stepId ${step.stepId} – steps are not topologically sorted, or source is missing.`, - ); - } - - /** - * The input source is from a step that is in a previous layer - */ - return inputGroupIndex < index; - }); - - return inputIsSatisfied; - }, - ); - - if (dependenciesAreInEarlierGroup) { - layer.push(step); - layerByStepId.set(step.stepId, index); - stepPlacedInLayer = true; - break; - } - } - - if (!stepPlacedInLayer) { - layers.push([step]); - layerByStepId.set(step.stepId, layers.length - 1); - } - } - - return { layers, layerByStepId }; -}; diff --git a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/swimlane.tsx b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/swimlane.tsx deleted file mode 100644 index 84f8db3710e..00000000000 --- a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/swimlane.tsx +++ /dev/null @@ -1,265 +0,0 @@ -import "reactflow/dist/style.css"; -import { Box, Fade, Stack, Typography } from "@mui/material"; -import ELK, { type ElkNode } from "elkjs"; -import { useEffect, useMemo } from "react"; -import ReactFlow, { - Background, - BackgroundVariant, - getNodesBounds, - useEdgesState, - useNodesState, - useReactFlow, -} from "reactflow"; - -import { customColors } from "@hashintel/design-system/theme"; - -import { - useFlowRunsContext, - useStatusForSteps, -} from "../../../../shared/flow-runs-context"; -import { flowRunStatusToStatusText } from "../../../../shared/flow-tables"; -import { MarkerEnd } from "./marker-end"; -import { nodeTabHeight, parentGroupPadding } from "./shared/dimensions"; -import { transitionOptions } from "./shared/styles"; -import { CustomEdge } from "./swimlane/custom-edge"; -import { CustomNode } from "./swimlane/custom-node"; -import { edgeColor } from "./swimlane/shared/edge-styles"; - -import type { CustomNodeType, EdgesAndNodes } from "./shared/types"; - -const nodeTypes = { - action: CustomNode, - "for-each": CustomNode, -}; - -const edgeTypes = { - "custom-edge": CustomEdge, -}; - -/** - * @see https://eclipse.dev/elk/documentation/tooldevelopers - * @see https://rtsys.informatik.uni-kiel.de/elklive/json.html for JSON playground - */ -const elk = new ELK(); - -const graphPadding = 30; - -/** - * @see https://eclipse.dev/elk/reference.html - */ -const elkLayoutOptions: ElkNode["layoutOptions"] = { - "elk.algorithm": "layered", - "org.eclipse.elk.layered.spacing.nodeNodeBetweenLayers": "100", - "elk.direction": "RIGHT", - "elk.padding": `[left=${graphPadding},top=${ - graphPadding - nodeTabHeight.offset - },right=${graphPadding},bottom=${graphPadding}]`, -}; - -const parentGroupLayoutOptions: ElkNode["layoutOptions"] = { - ...elkLayoutOptions, - "elk.padding": `[left=${parentGroupPadding.base}, top=${parentGroupPadding.top}, bottom=${parentGroupPadding.base}, right=${parentGroupPadding.base}]`, -}; - -type NodeWithChildren = CustomNodeType & { children: NodeWithChildren[] }; - -const flattedNodesToElkNodes = ( - allNodes: CustomNodeType[], - parents: CustomNodeType[], -): NodeWithChildren[] => { - return parents.map((parentNode) => ({ - ...parentNode, - layoutOptions: - parentNode.data.kind === "for-each" - ? parentGroupLayoutOptions - : undefined, - children: flattedNodesToElkNodes( - allNodes, - allNodes.filter((node) => node.parentNode === parentNode.id), - ), - })); -}; - -const elkGraphToFlattenedPositionedNodes = (nodes: ElkNode[]): ElkNode[] => { - return nodes.flatMap(({ children, ...node }) => [ - { ...node, position: { x: node.x, y: node.y } }, - ...elkGraphToFlattenedPositionedNodes(children ?? []), - ]); -}; - -export const Swimlane = ({ - nodes: initialNodes, - edges: initialEdges, -}: EdgesAndNodes) => { - const { fitView } = useReactFlow(); - - const { selectedFlowRun } = useFlowRunsContext(); - - const [nodes, setNodes, _onNodesChange] = useNodesState([]); - const [edges, setEdges, _onEdgesChange] = useEdgesState([]); - - useEffect(() => { - const childrenForElk = flattedNodesToElkNodes( - initialNodes, - initialNodes.filter((node) => !node.parentNode), - ); - - const graph: ElkNode = { - id: "root", - // @ts-expect-error –– mismatch between Elk and ReactFlow types - children: childrenForElk, - // @ts-expect-error –– mismatch between Elk and ReactFlow types - edges: initialEdges, - layoutOptions: elkLayoutOptions, - }; - - void elk - .layout(graph) - .then(({ children }) => { - return elkGraphToFlattenedPositionedNodes(children ?? []); - }) - .then((laidOutElements) => { - /** Reset the nodes to stop mismatch issues between old and new graph data, repeated ids etc, when switching flows */ - setNodes([]); - setEdges([]); - - setTimeout(() => { - // @ts-expect-error –– mismatch between Elk and ReactFlow types - setNodes(laidOutElements); - setEdges(initialEdges); - }, 0); - - // window.requestAnimationFrame(() => fitView()); - }); - }, [fitView, initialEdges, initialNodes, setNodes, setEdges]); - - const bounds = useMemo( - () => getNodesBounds(nodes.filter((node) => !node.parentNode)), - [nodes], - ); - - const stepsWithIds = useMemo( - () => - nodes.map((node) => ({ stepId: node.id, parentStepId: node.parentNode })), - [nodes], - ); - - const { overallStatus: groupStatus, statusByStep } = - useStatusForSteps(stepsWithIds) ?? {}; - - const statusToDisplay = useMemo(() => { - if (selectedFlowRun) { - /** - * If this is the only swimlane in the view, we want to show an error if the flow run is in an errored state - * – it may have errored before any steps in the swimlane have been processed. - */ - const flowRunStatus = flowRunStatusToStatusText(selectedFlowRun.status); - - if (["Abandoned", "Errored"].includes(flowRunStatus)) { - return flowRunStatus; - } - } - return groupStatus; - }, [groupStatus, selectedFlowRun]); - - /** - * We need to specify the markerEnd for each edge when they are passed into ReactFlow, - * because only markers which are referenced by edges passed to it will be included in its EdgeRenderer. - */ - const edgesWithLatestStatus = useMemo(() => { - return edges.map((edge) => { - const sourceStatus = statusByStep?.[edge.source] ?? "Waiting"; - return { - ...edge, - markerEnd: edgeColor[sourceStatus], - data: { sourceStatus }, - }; - }); - }, [edges, statusByStep]); - - return ( - - !selectedFlowRun - ? palette.gray[10] - : statusToDisplay === "Complete" - ? "rgba(239, 254, 250, 1)" - : statusToDisplay === "In Progress" - ? palette.blue[10] - : statusToDisplay === "Errored" || - statusToDisplay === "Cancelled" - ? palette.red[10] - : palette.common.white, - "&:not(:last-of-type)": { - borderBottom: ({ palette }) => `1px solid ${palette.gray[20]}`, - }, - flex: 1, - transition: ({ transitions }) => - transitions.create("background", transitionOptions), - }} - > - ({ - background: !selectedFlowRun ? "white" : "inherit", - minWidth: 170, - width: 170, - pl: 3, - pr: 4, - py: 2.5, - borderRight: `1px solid ${palette.gray[30]}`, - transition: ({ transitions }) => - transitions.create("background", { duration: 100 }), - })} - > - ({ - color: palette.gray[50], - })} - > - Flow - - - - - {[...new Set(Object.values(edgeColor))].map((color) => ( - - ))} - - -
- -
-
-
-
-
- ); -}; diff --git a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/swimlane/custom-edge.tsx b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/swimlane/custom-edge.tsx deleted file mode 100644 index 61fdad63225..00000000000 --- a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/swimlane/custom-edge.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { useMemo } from "react"; -import { BaseEdge, getSmoothStepPath } from "reactflow"; - -import { edgeColor } from "./shared/edge-styles"; - -import type { EdgeData } from "../shared/types"; -import type { EdgeProps } from "reactflow"; - -export const CustomEdge = ({ - id, - data, - markerEnd, - sourcePosition, - sourceX, - sourceY, - targetPosition, - targetX, - targetY, -}: EdgeProps) => { - const [edgePath] = getSmoothStepPath({ - sourcePosition, - sourceX, - sourceY, - targetPosition, - targetX: targetX - 8, - targetY, - borderRadius: 0, - }); - - const { sourceStatus = "Waiting" } = data ?? {}; - - const { style } = useMemo(() => { - const color = edgeColor[sourceStatus]; - - return { - style: { - stroke: color, - strokeWidth: 1, - }, - }; - }, [sourceStatus]); - - return ( - - ); -}; diff --git a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/swimlane/custom-node.tsx b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/swimlane/custom-node.tsx deleted file mode 100644 index 0758ab1f4d6..00000000000 --- a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/swimlane/custom-node.tsx +++ /dev/null @@ -1,256 +0,0 @@ -import { Box, Stack, Typography } from "@mui/material"; -import { formatDistance } from "date-fns"; -import { useEffect, useMemo, useState } from "react"; - -import { ArrowRightRegularIcon } from "@hashintel/design-system"; -import { parseStepRunId } from "@local/hash-isomorphic-utils/flows/step-run-id"; - -import { Button } from "../../../../../../shared/ui/button"; -import { - statusToSimpleStatus, - useFlowRunsContext, - useStatusForCurrentStep, -} from "../../../../../shared/flow-runs-context"; -import { QuestionModal } from "../shared/question-modal"; -import { Handles } from "./custom-node/handles"; -import { NodeContainer } from "./custom-node/node-container"; -import { statusSx } from "./custom-node/node-styles"; - -import type { - SimpleStatus, - StepRunStatus, -} from "../../../../../shared/flow-runs-context"; -import type { NodeData } from "../shared/types"; -import type { - ExternalInputRequest, - FlowActionDefinitionId, -} from "@local/hash-isomorphic-utils/flows/types"; -import type { SxProps, Theme } from "@mui/material"; -import type { NodeProps } from "reactflow"; - -const getTimeAgo = (isoString: string) => - formatDistance(new Date(isoString), new Date(), { - addSuffix: true, - }); - -const useStatusText = ({ - actionDefinitionId, - simpleStatusName, - statusData, -}: { - actionDefinitionId?: FlowActionDefinitionId; - simpleStatusName: SimpleStatus; - statusData?: StepRunStatus | null; -}) => { - return useMemo(() => { - if ( - actionDefinitionId && - actionDefinitionId === "researchEntities" && - simpleStatusName === "In Progress" - ) { - /** - * When the research task starts, which will often be at the start of a Flow, - * show a message while the agent is deciding whether to ask questions or immediately produce a plan. - * This provides a visual indicator that the triggering user should stick around to see if any questions are asked, - * which is replaced once the plan has been produced or questions have been asked. - */ - const hasCreatedPlan = statusData?.logs.some( - (log) => log.type === "CreatedPlan", - ); - if (!hasCreatedPlan) { - return "Deciding whether to ask questions..."; - } - } - - switch (simpleStatusName) { - case "Complete": - return "Successfully completed"; - case "In Progress": - return "Currently processing step..."; - case "Errored": - return "Step failed to complete"; - case "Cancelled": - return "Cancelled"; - case "Information Required": - return "Waiting for information from browser plugin"; - case "Skipped": - return "Skipped, because a value it needs is missing"; - default: - return "Waiting for earlier stages to finish"; - } - }, [actionDefinitionId, simpleStatusName, statusData]); -}; - -export const CustomNode = ({ data, id, selected }: NodeProps) => { - const [showQuestionModal, setShowQuestionModal] = useState(false); - - const statusData = useStatusForCurrentStep(); - - const { selectedFlowRun } = useFlowRunsContext(); - - let simpleStatusName = statusToSimpleStatus(statusData?.status ?? null); - - if ( - selectedFlowRun?.closedAt && - ["In Progress", "Waiting", "Information Required"].includes( - simpleStatusName, - ) - ) { - simpleStatusName = "Cancelled"; - } - - const statusText = useStatusText({ - actionDefinitionId: data.actionDefinition?.actionDefinitionId, - simpleStatusName, - statusData, - }); - - const outstandingInputRequest = useMemo(() => { - /** - * We'll take the first human input request that is not resolved, - * and if none are found return the first unresolved browser plugin request. - * - * There should only ever be a single human input request active, - * as they are asked by the top-level coordinating agent. - * - * There may be multiple browser web page requests, but the node's appearance - * should remain unchanged as they are resolved. - * - * We could alternatively show a count of outstanding requests. - */ - let browserInputRequest: ExternalInputRequest | undefined = undefined; - for (const request of selectedFlowRun?.inputRequests ?? []) { - /* A request is raised by a step run, which inside a for-each is one of several runs of this node's step. */ - if (request.resolvedAt ?? parseStepRunId(request.stepId).stepId !== id) { - continue; - } - if (request.type === "human-input") { - return request; - } - browserInputRequest ??= request; - } - return browserInputRequest; - }, [id, selectedFlowRun]); - - const { closedAt, scheduledAt } = statusData ?? {}; - - const isoString = closedAt ?? scheduledAt; - - const [timeAgo, setTimeAgo] = useState( - isoString ? getTimeAgo(isoString) : "", - ); - - const isParallelizedGroup = data.kind === "for-each"; - - const isParallelizedStep = data.inputSources.find( - (input) => input.kind === "item", - ); - - const styles = statusSx[simpleStatusName]; - - useEffect(() => { - let timeUpdateInterval: NodeJS.Timeout | undefined; - - if (isoString) { - setTimeAgo(getTimeAgo(isoString)); - - timeUpdateInterval = setInterval(() => { - setTimeAgo(getTimeAgo(isoString)); - }, 5_000); - } else { - setTimeAgo(""); - } - - return () => { - if (timeUpdateInterval) { - clearInterval(timeUpdateInterval); - } - }; - }, [isoString]); - - const commonStatusBarSx: SxProps = { - background: styles.lightestBackground, - borderRadius: 2.5, - p: 2, - transition: ({ transitions }) => transitions.create("background"), - }; - - const statusBarTextSx: SxProps = { - color: styles.text, - fontSize: 12, - fontWeight: 500, - textAlign: "center", - }; - - return ( - <> - {showQuestionModal && !!outstandingInputRequest && ( - setShowQuestionModal(false)} - /> - )} - - - - {data.label} - {isParallelizedStep ? "[]" : ""} - - - - {timeAgo} - - palette.gray[60], - }} - > - {timeAgo ? `(${isoString})` : ""} - - - - {!selectedFlowRun ? null : !isParallelizedGroup && - simpleStatusName === "Information Required" && - outstandingInputRequest && - outstandingInputRequest.type === "human-input" ? ( - - ) : ( - - {statusText} - - )} - - - - - - ); -}; diff --git a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/swimlane/custom-node/handles.tsx b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/swimlane/custom-node/handles.tsx deleted file mode 100644 index 41eee81b74d..00000000000 --- a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/swimlane/custom-node/handles.tsx +++ /dev/null @@ -1,217 +0,0 @@ -import { Box, Typography } from "@mui/material"; -import { useState } from "react"; -import { Handle as BaseHandle, Handle, Position, useEdges } from "reactflow"; - -import { Modal } from "../../../../../../../shared/ui/modal"; -import { nodeDimensions, nodeTabHeight } from "../../shared/dimensions"; -import { edgeColor } from "../shared/edge-styles"; - -import type { SimpleStatus } from "../../../../../../shared/flow-runs-context"; -import type { NodeData } from "../../shared/types"; -import type { HandleProps } from "reactflow"; - -type InputOrOutputBase = { - array?: boolean; - kind: "input" | "output"; - name: string; - required?: boolean; -}; - -type Input = InputOrOutputBase & { - kind: "input"; - source?: NodeData["inputSources"][number]; -}; - -type Output = InputOrOutputBase & { - kind: "output"; - source?: null; -}; - -type InputOrOutput = Input | Output; - -const CustomHandle = ({ - array, - name, - offset, - onClick, - position, - source, - type, -}: HandleProps & { offset: number; onClick: () => void } & InputOrOutput) => { - const constantValue = - source?.kind === "constant" ? source.payload.value : null; - - return ( - - {!constantValue && ( - - )} - ({ - fontSize: 12, - position: "absolute", - top: 4 + offset * 25, - ...(type === "target" ? { left: 3 } : { right: 3 }), - border: `1px solid ${palette.gray[30]}`, - borderRadius: 1, - px: 1, - height: 18, - "&:hover": { - border: `1px solid ${palette.gray[60]}`, - }, - transition: transitions.create("border"), - })} - > - {name} - {array ? "[]" : ""} - - - ); -}; - -const halfContentHeight = (nodeDimensions.height - nodeTabHeight.gross) / 2; - -export const Handles = ({ - kind, - inputSources, - actionDefinition, - stepId, - stepStatusName, -}: Pick & { - stepId: string; - stepStatusName: SimpleStatus; -}) => { - const [selectedProperty, setSelectedProperty] = - useState(null); - - const edges = useEdges(); - - if (!actionDefinition && inputSources.length === 0) { - return null; - } - - const showAllDependencies = false; - - const outputs: Output[] = (actionDefinition?.outputs ?? []).map( - ({ array, name, required }) => ({ - array, - kind: "output", - name, - required, - }), - ); - - const hideOutputHandle = - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- toggling to be added in follow up - !showAllDependencies && !edges.find((edge) => edge.source === stepId); - - const inputs: Input[] = []; - - for (const input of actionDefinition?.inputs ?? []) { - const existingSource = inputSources.find( - (source) => source.inputName === input.name, - ); - - inputs.push({ - name: input.name, - array: input.array, - kind: "input", - required: input.required, - source: existingSource, - }); - } - - if (kind === "for-each") { - /** - * A for-each step has no action definition, but does have an input source: the array it iterates over - */ - for (const source of inputSources) { - inputs.push({ - name: source.inputName, - kind: "input", - source, - }); - } - } - - const handleStyle = { - width: 12, - height: 12, - background: "white", - border: `1px solid ${edgeColor[stepStatusName]}`, - top: halfContentHeight, - transition: "border 0.2s ease-in-out", - }; - - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- toggling to be added in follow up - if (!showAllDependencies) { - return ( - <> - {!!inputs.length && ( - - )} - {!hideOutputHandle && !!outputs.length && ( - - )} - - ); - } - - return ( - <> - {selectedProperty && ( - setSelectedProperty(null)}> - - {selectedProperty.name} - - - )} - - {inputs.map((input, index) => ( - setSelectedProperty(input)} - type="target" - position={Position.Left} - {...input} - /> - ))} - {outputs.map((output, index) => ( - setSelectedProperty(output)} - type="source" - position={Position.Right} - {...output} - /> - ))} - - - ); -}; diff --git a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/swimlane/custom-node/node-container.tsx b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/swimlane/custom-node/node-container.tsx deleted file mode 100644 index e57bf60c380..00000000000 --- a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/swimlane/custom-node/node-container.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import { Box, Stack, Typography } from "@mui/material"; -import { useNodeId, useStore } from "reactflow"; - -import { useFlowRunsContext } from "../../../../../../shared/flow-runs-context"; -import { nodeTabHeight } from "../../shared/dimensions"; -import { transitionOptions } from "../../shared/styles"; -import { statusSx } from "./node-styles"; - -import type { SimpleStatus } from "../../../../../../shared/flow-runs-context"; -import type { StepDefinition } from "@local/hash-isomorphic-utils/flows/types"; -import type { PropsWithChildren } from "react"; - -const Tab = ({ - label, - position, - status, -}: { - label: string; - position: "left" | "right"; - status: SimpleStatus; -}) => { - const styles = statusSx[status]; - - return ( - ({ - background: - position === "left" ? styles.lightBackground : styles.darkBackground, - borderColor: styles.borderColor, - borderWidth: 1, - borderStyle: "solid", - borderBottomWidth: 0, - borderTopLeftRadius: "7px", - borderTopRightRadius: "7px", - height: nodeTabHeight.gross, - px: 1, - pt: 0.5, - pb: 2, - position: "relative", - top: nodeTabHeight.offset, - transition: transitions.create( - ["background", "borderColor"], - transitionOptions, - ), - zIndex: -1, - })} - > - ({ - color: - position === "left" - ? styles.text - : status === "Waiting" || status === "Skipped" - ? palette.gray[80] - : palette.common.white, - fontSize: 12, - fontWeight: 500, - transition: transitions.create("color", transitionOptions), - })} - > - {label} - - - ); -}; - -export const NodeContainer = ({ - children, - kind, - selected: _selected, - stepStatusName, -}: PropsWithChildren<{ - kind: StepDefinition["kind"]; - selected: boolean; - stepStatusName: SimpleStatus; -}>) => { - const nodeId = useNodeId(); - - const { selectedFlowRun } = useFlowRunsContext(); - - const size = useStore((state) => { - if (nodeId) { - const node = state.nodeInternals.get(nodeId); - if (node) { - return { - width: node.width, - height: node.height, - }; - } - } - }); - - const styles = statusSx[stepStatusName]; - - return ( - - - - {selectedFlowRun && kind !== "for-each" && ( - - )} - - ({ - background: palette.common.white, - borderColor: styles.borderColor, - borderWidth: 1, - borderStyle: "solid", - borderRadius: 3, - transition: transitions.create("borderColor"), - p: 2, - flex: 1, - position: "relative", - })} - > - {children} - - - ); -}; diff --git a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/swimlane/custom-node/node-styles.ts b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/swimlane/custom-node/node-styles.ts deleted file mode 100644 index 9b3fc4021e4..00000000000 --- a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/swimlane/custom-node/node-styles.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { SimpleStatus } from "../../../../../../shared/flow-runs-context"; -import type { SxProps, Theme } from "@mui/material"; - -const redStyles: SxProps = { - borderColor: ({ palette }) => palette.red[30], - darkBackground: ({ palette }) => palette.red[70], - lightBackground: ({ palette }) => palette.red[20], - lightestBackground: ({ palette }) => palette.red[10], - text: ({ palette }) => palette.red[100], -}; - -export const statusSx = { - Complete: { - borderColor: ({ palette }) => palette.green[30], - darkBackground: ({ palette }) => palette.green[70], - lightBackground: ({ palette }) => palette.green[20], - lightestBackground: ({ palette }) => palette.green[10], - text: ({ palette }) => palette.green[100], - }, - "In Progress": { - borderColor: ({ palette }) => palette.blue[30], - darkBackground: ({ palette }) => palette.blue[70], - lightBackground: ({ palette }) => palette.blue[20], - lightestBackground: ({ palette }) => palette.blue[10], - text: ({ palette }) => palette.blue[100], - }, - Cancelled: redStyles, - Errored: redStyles, - Waiting: { - borderColor: ({ palette }) => palette.gray[30], - darkBackground: ({ palette }) => palette.gray[30], - lightBackground: ({ palette }) => palette.gray[20], - lightestBackground: ({ palette }) => palette.gray[10], - text: ({ palette }) => palette.common.black, - }, - Skipped: { - borderColor: ({ palette }) => palette.gray[30], - darkBackground: ({ palette }) => palette.gray[50], - lightBackground: ({ palette }) => palette.gray[20], - lightestBackground: ({ palette }) => palette.gray[10], - text: ({ palette }) => palette.gray[70], - }, - "Information Required": { - borderColor: ({ palette }) => palette.yellow[40], - darkBackground: ({ palette }) => palette.yellow[80], - lightBackground: ({ palette }) => palette.yellow[30], - lightestBackground: ({ palette }) => palette.yellow[10], - text: ({ palette }) => palette.yellow[100], - }, -} as const satisfies Record>; diff --git a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/swimlane/shared/edge-styles.ts b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/swimlane/shared/edge-styles.ts deleted file mode 100644 index 14a3433d552..00000000000 --- a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/swimlane/shared/edge-styles.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { customColors } from "@hashintel/design-system/theme"; - -import type { SimpleStatus } from "../../../../../../shared/flow-runs-context"; - -export const edgeColor = { - Complete: customColors.green[70], - "In Progress": customColors.blue[70], - Cancelled: customColors.red[70], - Errored: customColors.red[70], - Waiting: customColors.gray[50], - Skipped: customColors.gray[50], - "Information Required": customColors.yellow[70], -} as const satisfies Record; diff --git a/apps/hash-frontend/src/pages/shared/flow-runs-context.ts b/apps/hash-frontend/src/pages/shared/flow-runs-context.ts index 57b9ecd92d2..72b3d971ab0 100644 --- a/apps/hash-frontend/src/pages/shared/flow-runs-context.ts +++ b/apps/hash-frontend/src/pages/shared/flow-runs-context.ts @@ -1,5 +1,4 @@ import { createContext, useContext, useMemo } from "react"; -import { useNodeId } from "reactflow"; import { parseStepRunId } from "@local/hash-isomorphic-utils/flows/step-run-id"; @@ -154,12 +153,6 @@ export const useStatusForStep = ( }, [selectedFlowRun, nodeId]); }; -export const useStatusForCurrentStep = (): StepRunStatus | null => { - const nodeId = useNodeId(); - - return useStatusForStep(nodeId); -}; - type StepsStatus = { closedAt?: string; scheduledAt?: string; diff --git a/libs/@local/flow-petri-net/README.md b/libs/@local/flow-petri-net/README.md index f2135637d6f..15319bdb874 100644 --- a/libs/@local/flow-petri-net/README.md +++ b/libs/@local/flow-petri-net/README.md @@ -16,7 +16,7 @@ flowchart LR validate["validateFlowDefinition"] net["SDCPN flow net"] engine["Temporal flow engine,
startFlow API"] - petrinaut["Petrinaut view
(TODO FE-1897)"] + petrinaut["Petrinaut view
on the flow page"] executor["Petri net executor
(TODO FE-1904)"] actions --> builder diff --git a/yarn.lock b/yarn.lock index 8c8723eacdf..42d1e9889be 100644 --- a/yarn.lock +++ b/yarn.lock @@ -704,6 +704,7 @@ __metadata: "@lit-labs/react": "npm:1.2.1" "@local/advanced-types": "workspace:*" "@local/eslint": "workspace:*" + "@local/flow-petri-net": "workspace:*" "@local/hash-graph-client": "workspace:*" "@local/hash-graph-sdk": "workspace:*" "@local/hash-graph-store": "workspace:*" @@ -818,7 +819,6 @@ __metadata: react-transition-group: "npm:4.4.5" react-virtuoso: "npm:4.14.1" react-window: "npm:1.8.11" - reactflow: "npm:11.11.4" recharts: "npm:3.8.0" remark-gfm: "npm:4.0.1" rimraf: "npm:6.1.3" @@ -9707,7 +9707,7 @@ __metadata: languageName: unknown linkType: soft -"@local/flow-petri-net@workspace:libs/@local/flow-petri-net": +"@local/flow-petri-net@workspace:*, @local/flow-petri-net@workspace:libs/@local/flow-petri-net": version: 0.0.0-use.local resolution: "@local/flow-petri-net@workspace:libs/@local/flow-petri-net" dependencies: @@ -14413,102 +14413,6 @@ __metadata: languageName: node linkType: hard -"@reactflow/background@npm:11.3.14": - version: 11.3.14 - resolution: "@reactflow/background@npm:11.3.14" - dependencies: - "@reactflow/core": "npm:11.11.4" - classcat: "npm:^5.0.3" - zustand: "npm:^4.4.1" - peerDependencies: - react: ">=17" - react-dom: ">=17" - checksum: 10c0/a4ef8ca38f9cfd72d578dd2b2672d5bf841410f5a399e0c1d7bf4ea10e5d76003dec2fee15f927d4d5f121fc4414c811f2c4b9e702d28e31a4bd9399fc8cbe29 - languageName: node - linkType: hard - -"@reactflow/controls@npm:11.2.14": - version: 11.2.14 - resolution: "@reactflow/controls@npm:11.2.14" - dependencies: - "@reactflow/core": "npm:11.11.4" - classcat: "npm:^5.0.3" - zustand: "npm:^4.4.1" - peerDependencies: - react: ">=17" - react-dom: ">=17" - checksum: 10c0/6cd79e66590e301e5b3034dccfa1dcfb01fe27bbd1b8aadf8afaa8a0f68e3abe1c3d4a90a6e1ea54eecf5acb019073ead7379fbbf1252af06a63e413f82a74dc - languageName: node - linkType: hard - -"@reactflow/core@npm:11.11.4": - version: 11.11.4 - resolution: "@reactflow/core@npm:11.11.4" - dependencies: - "@types/d3": "npm:^7.4.0" - "@types/d3-drag": "npm:^3.0.1" - "@types/d3-selection": "npm:^3.0.3" - "@types/d3-zoom": "npm:^3.0.1" - classcat: "npm:^5.0.3" - d3-drag: "npm:^3.0.0" - d3-selection: "npm:^3.0.0" - d3-zoom: "npm:^3.0.0" - zustand: "npm:^4.4.1" - peerDependencies: - react: ">=17" - react-dom: ">=17" - checksum: 10c0/895a0c1491ba81a6ed06ee9f2619870083753c6faf26ad6cb37a5c27f2f2fa80ab88d4be5c77fb7cad17ba6b20b212ea3171a976f3817056196cf85f6d90e86c - languageName: node - linkType: hard - -"@reactflow/minimap@npm:11.7.14": - version: 11.7.14 - resolution: "@reactflow/minimap@npm:11.7.14" - dependencies: - "@reactflow/core": "npm:11.11.4" - "@types/d3-selection": "npm:^3.0.3" - "@types/d3-zoom": "npm:^3.0.1" - classcat: "npm:^5.0.3" - d3-selection: "npm:^3.0.0" - d3-zoom: "npm:^3.0.0" - zustand: "npm:^4.4.1" - peerDependencies: - react: ">=17" - react-dom: ">=17" - checksum: 10c0/5bc99ab12e4daafacfb3d60e36c7d2253cfd7cd7ba5765a9ff7c35a3a6835c49cae9aa1acde181baf5b2de3e2c48226758f5a61125ecf7014a9d58fe6ef62bb6 - languageName: node - linkType: hard - -"@reactflow/node-resizer@npm:2.2.14": - version: 2.2.14 - resolution: "@reactflow/node-resizer@npm:2.2.14" - dependencies: - "@reactflow/core": "npm:11.11.4" - classcat: "npm:^5.0.4" - d3-drag: "npm:^3.0.0" - d3-selection: "npm:^3.0.0" - zustand: "npm:^4.4.1" - peerDependencies: - react: ">=17" - react-dom: ">=17" - checksum: 10c0/094905bc9eb4ec9e20ebe732983a44895822f7fae56c8d348b478bbee776342b286f08578c57eeeb5408a931febced32a5fef68163755b37fc19ca523b78e0dd - languageName: node - linkType: hard - -"@reactflow/node-toolbar@npm:1.3.14": - version: 1.3.14 - resolution: "@reactflow/node-toolbar@npm:1.3.14" - dependencies: - "@reactflow/core": "npm:11.11.4" - classcat: "npm:^5.0.3" - zustand: "npm:^4.4.1" - peerDependencies: - react: ">=17" - react-dom: ">=17" - checksum: 10c0/cd29257969a11feb273849d3aa540d534513b292115ebf697db1357fae704426be899dcf882ea36e13a33d49073b6c2b44fc67c4ddbc779bf2dcd49ba87053a3 - languageName: node - linkType: hard - "@redis/bloom@npm:5.12.1": version: 5.12.1 resolution: "@redis/bloom@npm:5.12.1" @@ -18272,38 +18176,13 @@ __metadata: languageName: node linkType: hard -"@types/d3-array@npm:*, @types/d3-array@npm:^3.0.3": +"@types/d3-array@npm:^3.0.3": version: 3.2.2 resolution: "@types/d3-array@npm:3.2.2" checksum: 10c0/6137cb97302f8a4f18ca22c0560c585cfcb823f276b23d89f2c0c005d72697ec13bca671c08e68b4b0cabd622e3f0e91782ee221580d6774074050be96dd7028 languageName: node linkType: hard -"@types/d3-axis@npm:*": - version: 3.0.6 - resolution: "@types/d3-axis@npm:3.0.6" - dependencies: - "@types/d3-selection": "npm:*" - checksum: 10c0/d756d42360261f44d8eefd0950c5bb0a4f67a46dd92069da3f723ac36a1e8cb2b9ce6347d836ef19d5b8aef725dbcf8fdbbd6cfbff676ca4b0642df2f78b599a - languageName: node - linkType: hard - -"@types/d3-brush@npm:*": - version: 3.0.6 - resolution: "@types/d3-brush@npm:3.0.6" - dependencies: - "@types/d3-selection": "npm:*" - checksum: 10c0/fd6e2ac7657a354f269f6b9c58451ffae9d01b89ccb1eb6367fd36d635d2f1990967215ab498e0c0679ff269429c57fad6a2958b68f4d45bc9f81d81672edc01 - languageName: node - linkType: hard - -"@types/d3-chord@npm:*": - version: 3.0.6 - resolution: "@types/d3-chord@npm:3.0.6" - checksum: 10c0/c5a25eb5389db01e63faec0c5c2ec7cc41c494e9b3201630b494c4e862a60f1aa83fabbc33a829e7e1403941e3c30d206c741559b14406ac2a4239cfdf4b4c17 - languageName: node - linkType: hard - "@types/d3-color@npm:*": version: 3.1.3 resolution: "@types/d3-color@npm:3.1.3" @@ -18311,31 +18190,7 @@ __metadata: languageName: node linkType: hard -"@types/d3-contour@npm:*": - version: 3.0.6 - resolution: "@types/d3-contour@npm:3.0.6" - dependencies: - "@types/d3-array": "npm:*" - "@types/geojson": "npm:*" - checksum: 10c0/e7d83e94719af4576ceb5ac7f277c5806f83ba6c3631744ae391cffc3641f09dfa279470b83053cd0b2acd6784e8749c71141d05bdffa63ca58ffb5b31a0f27c - languageName: node - linkType: hard - -"@types/d3-delaunay@npm:*": - version: 6.0.4 - resolution: "@types/d3-delaunay@npm:6.0.4" - checksum: 10c0/d154a8864f08c4ea23ecb9bdabcef1c406a25baa8895f0cb08a0ed2799de0d360e597552532ce7086ff0cdffa8f3563f9109d18f0191459d32bb620a36939123 - languageName: node - linkType: hard - -"@types/d3-dispatch@npm:*": - version: 3.0.6 - resolution: "@types/d3-dispatch@npm:3.0.6" - checksum: 10c0/405eb7d0ec139fbf72fa6a43b0f3ca8a1f913bb2cb38f607827e63fca8d4393f021f32f3b96b33c93ddbd37789453a0b3624f14f504add5308fd9aec8a46dda0 - languageName: node - linkType: hard - -"@types/d3-drag@npm:*, @types/d3-drag@npm:^3.0.1, @types/d3-drag@npm:^3.0.7": +"@types/d3-drag@npm:^3.0.7": version: 3.0.7 resolution: "@types/d3-drag@npm:3.0.7" dependencies: @@ -18344,59 +18199,13 @@ __metadata: languageName: node linkType: hard -"@types/d3-dsv@npm:*": - version: 3.0.7 - resolution: "@types/d3-dsv@npm:3.0.7" - checksum: 10c0/c0f01da862465594c8a28278b51c850af3b4239cc22b14fd1a19d7a98f93d94efa477bf59d8071beb285dca45bf614630811451e18e7c52add3a0abfee0a1871 - languageName: node - linkType: hard - -"@types/d3-ease@npm:*, @types/d3-ease@npm:^3.0.0": +"@types/d3-ease@npm:^3.0.0": version: 3.0.2 resolution: "@types/d3-ease@npm:3.0.2" checksum: 10c0/aff5a1e572a937ee9bff6465225d7ba27d5e0c976bd9eacdac2e6f10700a7cb0c9ea2597aff6b43a6ed850a3210030870238894a77ec73e309b4a9d0333f099c languageName: node linkType: hard -"@types/d3-fetch@npm:*": - version: 3.0.7 - resolution: "@types/d3-fetch@npm:3.0.7" - dependencies: - "@types/d3-dsv": "npm:*" - checksum: 10c0/3d147efa52a26da1a5d40d4d73e6cebaaa964463c378068062999b93ea3731b27cc429104c21ecbba98c6090e58ef13429db6399238c5e3500162fb3015697a0 - languageName: node - linkType: hard - -"@types/d3-force@npm:*": - version: 3.0.10 - resolution: "@types/d3-force@npm:3.0.10" - checksum: 10c0/c82b459079a106b50e346c9b79b141f599f2fc4f598985a5211e72c7a2e20d35bd5dc6e91f306b323c8bfa325c02c629b1645f5243f1c6a55bd51bc85cccfa92 - languageName: node - linkType: hard - -"@types/d3-format@npm:*": - version: 3.0.4 - resolution: "@types/d3-format@npm:3.0.4" - checksum: 10c0/3ac1600bf9061a59a228998f7cd3f29e85cbf522997671ba18d4d84d10a2a1aff4f95aceb143fa9960501c3ec351e113fc75884e6a504ace44dc1744083035ee - languageName: node - linkType: hard - -"@types/d3-geo@npm:*": - version: 3.1.0 - resolution: "@types/d3-geo@npm:3.1.0" - dependencies: - "@types/geojson": "npm:*" - checksum: 10c0/3745a93439038bb5b0b38facf435f7079812921d46406f5d38deaee59e90084ff742443c7ea0a8446df81a0d81eaf622fe7068cf4117a544bd4aa3b2dc182f88 - languageName: node - linkType: hard - -"@types/d3-hierarchy@npm:*": - version: 3.1.7 - resolution: "@types/d3-hierarchy@npm:3.1.7" - checksum: 10c0/873711737d6b8e7b6f1dda0bcd21294a48f75024909ae510c5d2c21fad2e72032e0958def4d9f68319d3aaac298ad09c49807f8bfc87a145a82693b5208613c7 - languageName: node - linkType: hard - "@types/d3-interpolate@npm:*, @types/d3-interpolate@npm:^3.0.1, @types/d3-interpolate@npm:^3.0.4": version: 3.0.4 resolution: "@types/d3-interpolate@npm:3.0.4" @@ -18413,35 +18222,7 @@ __metadata: languageName: node linkType: hard -"@types/d3-polygon@npm:*": - version: 3.0.2 - resolution: "@types/d3-polygon@npm:3.0.2" - checksum: 10c0/f46307bb32b6c2aef8c7624500e0f9b518de8f227ccc10170b869dc43e4c542560f6c8d62e9f087fac45e198d6e4b623e579c0422e34c85baf56717456d3f439 - languageName: node - linkType: hard - -"@types/d3-quadtree@npm:*": - version: 3.0.6 - resolution: "@types/d3-quadtree@npm:3.0.6" - checksum: 10c0/7eaa0a4d404adc856971c9285e1c4ab17e9135ea669d847d6db7e0066126a28ac751864e7ce99c65d526e130f56754a2e437a1617877098b3bdcc3ef23a23616 - languageName: node - linkType: hard - -"@types/d3-random@npm:*": - version: 3.0.3 - resolution: "@types/d3-random@npm:3.0.3" - checksum: 10c0/5f4fea40080cd6d4adfee05183d00374e73a10c530276a6455348983dda341003a251def28565a27c25d9cf5296a33e870e397c9d91ff83fb7495a21c96b6882 - languageName: node - linkType: hard - -"@types/d3-scale-chromatic@npm:*": - version: 3.0.3 - resolution: "@types/d3-scale-chromatic@npm:3.0.3" - checksum: 10c0/2f48c6f370edba485b57b73573884ded71914222a4580140ff87ee96e1d55ccd05b1d457f726e234a31269b803270ac95d5554229ab6c43c7e4a9894e20dd490 - languageName: node - linkType: hard - -"@types/d3-scale@npm:*, @types/d3-scale@npm:^4.0.2": +"@types/d3-scale@npm:^4.0.2": version: 4.0.9 resolution: "@types/d3-scale@npm:4.0.9" dependencies: @@ -18450,14 +18231,14 @@ __metadata: languageName: node linkType: hard -"@types/d3-selection@npm:*, @types/d3-selection@npm:^3.0.10, @types/d3-selection@npm:^3.0.3": +"@types/d3-selection@npm:*, @types/d3-selection@npm:^3.0.10": version: 3.0.11 resolution: "@types/d3-selection@npm:3.0.11" checksum: 10c0/0c512956c7503ff5def4bb32e0c568cc757b9a2cc400a104fc0f4cfe5e56d83ebde2a97821b6f2cb26a7148079d3b86a2f28e11d68324ed311cf35c2ed980d1d languageName: node linkType: hard -"@types/d3-shape@npm:*, @types/d3-shape@npm:^3.1.0": +"@types/d3-shape@npm:^3.1.0": version: 3.1.8 resolution: "@types/d3-shape@npm:3.1.8" dependencies: @@ -18466,13 +18247,6 @@ __metadata: languageName: node linkType: hard -"@types/d3-time-format@npm:*": - version: 4.0.3 - resolution: "@types/d3-time-format@npm:4.0.3" - checksum: 10c0/9ef5e8e2b96b94799b821eed5d61a3d432c7903247966d8ad951b8ce5797fe46554b425cb7888fa5bf604b4663c369d7628c0328ffe80892156671c58d1a7f90 - languageName: node - linkType: hard - "@types/d3-time@npm:*, @types/d3-time@npm:^3.0.0": version: 3.0.4 resolution: "@types/d3-time@npm:3.0.4" @@ -18480,14 +18254,14 @@ __metadata: languageName: node linkType: hard -"@types/d3-timer@npm:*, @types/d3-timer@npm:^3.0.0": +"@types/d3-timer@npm:^3.0.0": version: 3.0.2 resolution: "@types/d3-timer@npm:3.0.2" checksum: 10c0/c644dd9571fcc62b1aa12c03bcad40571553020feeb5811f1d8a937ac1e65b8a04b759b4873aef610e28b8714ac71c9885a4d6c127a048d95118f7e5b506d9e1 languageName: node linkType: hard -"@types/d3-transition@npm:*, @types/d3-transition@npm:^3.0.8": +"@types/d3-transition@npm:^3.0.8": version: 3.0.9 resolution: "@types/d3-transition@npm:3.0.9" dependencies: @@ -18496,7 +18270,7 @@ __metadata: languageName: node linkType: hard -"@types/d3-zoom@npm:*, @types/d3-zoom@npm:^3.0.1, @types/d3-zoom@npm:^3.0.8": +"@types/d3-zoom@npm:^3.0.8": version: 3.0.8 resolution: "@types/d3-zoom@npm:3.0.8" dependencies: @@ -18506,44 +18280,6 @@ __metadata: languageName: node linkType: hard -"@types/d3@npm:^7.4.0": - version: 7.4.3 - resolution: "@types/d3@npm:7.4.3" - dependencies: - "@types/d3-array": "npm:*" - "@types/d3-axis": "npm:*" - "@types/d3-brush": "npm:*" - "@types/d3-chord": "npm:*" - "@types/d3-color": "npm:*" - "@types/d3-contour": "npm:*" - "@types/d3-delaunay": "npm:*" - "@types/d3-dispatch": "npm:*" - "@types/d3-drag": "npm:*" - "@types/d3-dsv": "npm:*" - "@types/d3-ease": "npm:*" - "@types/d3-fetch": "npm:*" - "@types/d3-force": "npm:*" - "@types/d3-format": "npm:*" - "@types/d3-geo": "npm:*" - "@types/d3-hierarchy": "npm:*" - "@types/d3-interpolate": "npm:*" - "@types/d3-path": "npm:*" - "@types/d3-polygon": "npm:*" - "@types/d3-quadtree": "npm:*" - "@types/d3-random": "npm:*" - "@types/d3-scale": "npm:*" - "@types/d3-scale-chromatic": "npm:*" - "@types/d3-selection": "npm:*" - "@types/d3-shape": "npm:*" - "@types/d3-time": "npm:*" - "@types/d3-time-format": "npm:*" - "@types/d3-timer": "npm:*" - "@types/d3-transition": "npm:*" - "@types/d3-zoom": "npm:*" - checksum: 10c0/a9c6d65b13ef3b42c87f2a89ea63a6d5640221869f97d0657b0cb2f1dac96a0f164bf5605643c0794e0de3aa2bf05df198519aaf15d24ca135eb0e8bd8a9d879 - languageName: node - linkType: hard - "@types/debug@npm:^4.0.0": version: 4.1.12 resolution: "@types/debug@npm:4.1.12" @@ -18704,7 +18440,7 @@ __metadata: languageName: node linkType: hard -"@types/geojson@npm:*, @types/geojson@npm:^7946.0.16, @types/geojson@npm:^7946.0.7": +"@types/geojson@npm:^7946.0.16, @types/geojson@npm:^7946.0.7": version: 7946.0.16 resolution: "@types/geojson@npm:7946.0.16" checksum: 10c0/1ff24a288bd5860b766b073ead337d31d73bdc715e5b50a2cee5cb0af57a1ed02cc04ef295f5fa68dc40fe3e4f104dd31282b2b818a5ba3231bc1001ba084e3c @@ -24020,7 +23756,7 @@ __metadata: languageName: node linkType: hard -"classcat@npm:^5.0.3, classcat@npm:^5.0.4": +"classcat@npm:^5.0.3": version: 5.0.5 resolution: "classcat@npm:5.0.5" checksum: 10c0/ff8d273055ef9b518529cfe80fd0486f7057a9917373807ff802d75ceb46e8f8e148f41fa094ee7625c8f34642cfaa98395ff182d9519898da7cbf383d4a210d @@ -39054,23 +38790,6 @@ __metadata: languageName: node linkType: hard -"reactflow@npm:11.11.4": - version: 11.11.4 - resolution: "reactflow@npm:11.11.4" - dependencies: - "@reactflow/background": "npm:11.3.14" - "@reactflow/controls": "npm:11.2.14" - "@reactflow/core": "npm:11.11.4" - "@reactflow/minimap": "npm:11.7.14" - "@reactflow/node-resizer": "npm:2.2.14" - "@reactflow/node-toolbar": "npm:1.3.14" - peerDependencies: - react: ">=17" - react-dom: ">=17" - checksum: 10c0/5faf9895db29d39c190b78a6113a4f97430d576086efeb3570deec81035559297823dd9ca96b9dd7daeb17159a12ac9117e399e71696485e6ccf43e9f98c1f35 - languageName: node - linkType: hard - "read-package-json-fast@npm:^4.0.0": version: 4.0.0 resolution: "read-package-json-fast@npm:4.0.0" @@ -46043,7 +45762,7 @@ __metadata: languageName: node linkType: hard -"zustand@npm:^4.4.0, zustand@npm:^4.4.1": +"zustand@npm:^4.4.0": version: 4.5.7 resolution: "zustand@npm:4.5.7" dependencies: