diff --git a/ui/src/components/pipeline/Pipeline.tsx b/ui/src/components/pipeline/Pipeline.tsx index aa258e7cf..fa8c89757 100644 --- a/ui/src/components/pipeline/Pipeline.tsx +++ b/ui/src/components/pipeline/Pipeline.tsx @@ -4,7 +4,7 @@ import { ConnectionLineType, Edge, Node } from "react-flow-renderer"; import Graph from "./graph/Graph"; import { usePipelineFetch } from "../../utils/fetchWrappers/pipelineFetch"; import { useEdgesInfoFetch } from "../../utils/fetchWrappers/edgeInfoFetch"; - +import { ProcessingRates } from "../../utils/models/pipeline"; import "./Pipeline.css"; export function Pipeline() { @@ -14,19 +14,24 @@ export function Pipeline() { const [vertexPods, setVertexPods] = useState>(null); - const { - pipeline, - error: pipelineError, - } = usePipelineFetch(namespaceId, pipelineId, pipelineRequestKey); + const [vertexRate, setVertexRate] = + useState>(null); + + const { pipeline, error: pipelineError } = usePipelineFetch( + namespaceId, + pipelineId, + pipelineRequestKey + ); const [edgesInfoRequestKey, setEdgesInfoRequestKey] = useState( `${Date.now()}` ); - const { - edgesInfo, - error: edgesInfoError, - } = useEdgesInfoFetch(namespaceId, pipelineId, edgesInfoRequestKey); + const { edgesInfo, error: edgesInfoError } = useEdgesInfoFetch( + namespaceId, + pipelineId, + edgesInfoRequestKey + ); useEffect(() => { // Refresh pipeline info every x ms @@ -66,9 +71,35 @@ export function Pipeline() { } }, [pipeline]); + // This useEffect is used to obtain all the rates for a given vertex in a pipeline. + useEffect(() => { + const vertexToRateMap = new Map(); + + if (pipeline?.spec?.vertices) { + Promise.all( + pipeline?.spec?.vertices.map((vertex) => { + return fetch( + `/api/v1/namespaces/${namespaceId}/pipelines/${pipelineId}/vertices/${vertex.name}/metrics` + ) + .then((response) => response.json()) + .then((json) => { + const processinRates = {} as ProcessingRates; + processinRates.ratePerMin = + json["processingRates"]["1m"].toFixed(2); + processinRates.ratePerFiveMin = + json["processingRates"]["5m"].toFixed(2); + processinRates.ratePerFifteenMin = + json["processingRates"]["15m"].toFixed(2); + vertexToRateMap.set(vertex.name, processinRates); + }); + }) + ).then(() => setVertexRate(vertexToRateMap)); + } + }, [pipeline]); + const vertices = useMemo(() => { const newVertices: Node[] = []; - if (pipeline?.spec?.vertices && vertexPods) { + if (pipeline?.spec?.vertices && vertexPods && vertexRate) { pipeline.spec.vertices.map((vertex) => { const podsLength = vertexPods.has(vertex.name) ? vertexPods.get(vertex.name) @@ -86,39 +117,24 @@ export function Pipeline() { // change this in the future if you would like to make it draggable newNode.draggable = false; if (vertex.source) { - newNode.type = "input"; - newNode.style = { - background: "#d5dee6", - boxShadow: "1", - color: "#333", - border: "1px solid #b8cee2", - }; + newNode.type = "source"; newNode.data.source = vertex; } else if (vertex.sink) { - newNode.type = "output"; - newNode.style = { - background: "#c3bbb7", - color: "#333", - border: "1px solid #a9a3a0", - }; + newNode.type = "sink"; newNode.data.sink = vertex; + newNode.data.test = vertex.name; } else { newNode.data.udf = vertex; - newNode.style = { - background: "#efdbce", - color: "#333", - border: "1px solid #f1c5a8", - }; + newNode.type = "udf"; } - newNode.style.cursor = "pointer"; - newNode.style.fontFamily = "IBM Plex Sans"; - newNode.style.fontWeight = 400; - newNode.style.fontSize = "0.50rem"; + newNode.data.rate = vertexRate.has(vertex.name) + ? vertexRate.get(vertex.name) + : 0; newVertices.push(newNode); }); } return newVertices; - }, [pipeline, vertexPods]); + }, [pipeline, vertexPods, vertexRate]); const edges = useMemo(() => { const newEdges: Edge[] = []; diff --git a/ui/src/components/pipeline/graph/Graph.test.tsx b/ui/src/components/pipeline/graph/Graph.test.tsx index c7f486384..6811cb633 100644 --- a/ui/src/components/pipeline/graph/Graph.test.tsx +++ b/ui/src/components/pipeline/graph/Graph.test.tsx @@ -1,278 +1,338 @@ -import Graph from "./Graph" -import {render, screen, waitFor} from "@testing-library/react" -import {Position} from "react-flow-renderer"; +import Graph from "./Graph"; +import { render, screen, waitFor } from "@testing-library/react"; +import { Position } from "react-flow-renderer"; -global.ResizeObserver = require('resize-observer-polyfill') +global.ResizeObserver = require("resize-observer-polyfill"); describe("Graph screen test", () => { + const data = { + vertices: [ + { + id: "input", + data: { + label: "input", + source: { generator: { rpu: 250, duration: "1s", msgSize: 8 } }, + }, + position: { x: 86.00022929179097, y: 104 }, + type: "input", + style: { + background: "#D6D5E6", + color: "#333", + border: "1px solid #222138", + }, + targetPosition: Position.Left, + sourcePosition: Position.Right, + }, + { + id: "preproc", + data: { + label: "preproc", + udf: { container: null, builtin: { name: "cat" } }, + }, + position: { x: 308.0009479912802, y: 104 }, + targetPosition: Position.Left, + sourcePosition: Position.Right, + }, + { + id: "train", + data: { + label: "train", + udf: { container: null, builtin: { name: "cat" } }, + }, + position: { x: 752.0003643437127, y: 18 }, + targetPosition: Position.Left, + sourcePosition: Position.Right, + }, + { + id: "train-1", + data: { + label: "train-1", + udf: { container: null, builtin: { name: "cat" } }, + }, + position: { x: 974.0005434138947, y: 18 }, + targetPosition: Position.Left, + sourcePosition: Position.Right, + }, + { + id: "infer", + data: { + label: "infer", + udf: { container: null, builtin: { name: "cat" } }, + }, + position: { x: 530.0006491028594, y: 104 }, + targetPosition: Position.Left, + sourcePosition: Position.Right, + }, + { + id: "postproc", + data: { + label: "postproc", + udf: { container: null, builtin: { name: "cat" } }, + }, + position: { x: 752.0008060721026, y: 147 }, + targetPosition: Position.Left, + sourcePosition: Position.Right, + }, + { + id: "log-output", + data: { label: "log-output", sink: { log: {} } }, + position: { x: 974.0001823872657, y: 104 }, + type: "output", + style: { + background: "#D6D5E6", + color: "#333", + border: "1px solid #222138", + }, + targetPosition: Position.Left, + sourcePosition: Position.Right, + }, + { + id: "train-output", + data: { label: "train-output", sink: { log: {} } }, + position: { x: 1196.0000658161566, y: 18 }, + type: "output", + style: { + background: "#D6D5E6", + color: "#333", + border: "1px solid #222138", + }, + targetPosition: Position.Left, + sourcePosition: Position.Right, + }, + { + id: "publisher", + data: { label: "publisher", sink: { log: {} } }, + position: { x: 974.0002039119054, y: 190 }, + type: "output", + style: { + background: "#D6D5E6", + color: "#333", + border: "1px solid #222138", + }, + targetPosition: Position.Left, + sourcePosition: Position.Right, + }, + ], + edges: [ + { + id: "input-preproc", + label: "0", + source: "input", + target: "preproc", + data: { + pipeline: "simple-pipeline", + fromVertex: "input", + toVertex: "preproc", + bufferName: "numaflow-system-simple-pipeline-postproc-publisher", + pendingCount: 0, + ackPendingCount: 0, + totalMessages: 0, + bufferLength: 10000, + bufferUsageLimit: 0.8, + bufferUsage: 0, + isFull: false, + conditions: null, + label: "0", + }, + animated: true, + type: "smoothstep", + }, + { + id: "preproc-infer", + label: "0", + source: "preproc", + target: "infer", + data: { + pipeline: "simple-pipeline", + fromVertex: "preproc", + toVertex: "infer", + bufferName: "numaflow-system-simple-pipeline-postproc-publisher", + pendingCount: 0, + ackPendingCount: 0, + totalMessages: 0, + bufferLength: 10000, + bufferUsageLimit: 0.8, + bufferUsage: 0, + isFull: false, + conditions: null, + label: "0", + }, + animated: true, + type: "smoothstep", + }, + { + id: "infer-train", + label: "0", + source: "infer", + target: "train", + data: { + pipeline: "simple-pipeline", + fromVertex: "infer", + toVertex: "train", + bufferName: "numaflow-system-simple-pipeline-postproc-publisher", + pendingCount: 0, + ackPendingCount: 0, + totalMessages: 18, + bufferLength: 10000, + bufferUsageLimit: 0.8, + bufferUsage: 0, + isFull: false, + conditions: { keyIn: ["train"] }, + label: "0", + }, + animated: true, + type: "smoothstep", + }, + { + id: "train-train-1", + label: "49", + source: "train", + target: "train-1", + data: { + pipeline: "simple-pipeline", + fromVertex: "train", + toVertex: "train-1", + bufferName: "numaflow-system-simple-pipeline-postproc-publisher", + pendingCount: 19, + ackPendingCount: 30, + totalMessages: 49, + bufferLength: 10000, + bufferUsageLimit: 0.8, + bufferUsage: 0.0049, + isFull: false, + conditions: null, + label: "49", + }, + animated: true, + type: "smoothstep", + }, + { + id: "train-1-train-output", + label: "0", + source: "train-1", + target: "train-output", + data: { + pipeline: "simple-pipeline", + fromVertex: "train-1", + toVertex: "train-output", + bufferName: "numaflow-system-simple-pipeline-postproc-publisher", + pendingCount: 0, + ackPendingCount: 0, + totalMessages: 30, + bufferLength: 10000, + bufferUsageLimit: 0.8, + bufferUsage: 0, + isFull: false, + conditions: null, + label: "0", + }, + animated: true, + type: "smoothstep", + }, + { + id: "infer-postproc", + label: "53", + source: "infer", + target: "postproc", + data: { + pipeline: "simple-pipeline", + fromVertex: "infer", + toVertex: "postproc", + bufferName: "numaflow-system-simple-pipeline-postproc-publisher", + pendingCount: 0, + ackPendingCount: 53, + totalMessages: 53, + bufferLength: 10000, + bufferUsageLimit: 0.8, + bufferUsage: 0.0053, + isFull: false, + conditions: { keyIn: ["postproc"] }, + label: "53", + }, + animated: true, + type: "smoothstep", + }, + { + id: "postproc-log-output", + label: "0", + source: "postproc", + target: "log-output", + data: { + pipeline: "simple-pipeline", + fromVertex: "postproc", + toVertex: "log-output", + bufferName: "numaflow-system-simple-pipeline-postproc-publisher", + pendingCount: 0, + ackPendingCount: 0, + totalMessages: 0, + bufferLength: 10000, + bufferUsageLimit: 0.8, + bufferUsage: 0, + isFull: false, + conditions: null, + label: "0", + }, + animated: true, + type: "smoothstep", + }, + { + id: "postproc-publisher", + label: "0", + source: "postproc", + target: "publisher", + data: { + pipeline: "simple-pipeline", + fromVertex: "postproc", + toVertex: "publisher", + bufferName: "numaflow-system-simple-pipeline-postproc-publisher", + pendingCount: 0, + ackPendingCount: 0, + totalMessages: 49, + bufferLength: 10000, + bufferUsageLimit: 0.8, + bufferUsage: 0, + isFull: false, + conditions: null, + label: "0", + }, + animated: true, + type: "smoothstep", + }, + ], + status: { + conditions: [ + { + type: "Configured", + status: "True", + lastTransitionTime: "2022-05-19T21:06:02Z", + reason: "Successful", + message: "Successful", + }, + { + type: "Deployed", + status: "True", + lastTransitionTime: "2022-05-19T21:06:02Z", + reason: "Successful", + message: "Successful", + }, + ], + phase: "Running", + lastUpdated: "2022-05-19T21:06:02Z", + }, + }; - const data = { - "vertices": [{ - "id": "input", - "data": {"label": "input", "source": {"generator": {"rpu": 250, "duration": "1s", "msgSize": 8}}}, - "position": {"x": 86.00022929179097, "y": 104}, - "type": "input", - "style": {"background": "#D6D5E6", "color": "#333", "border": "1px solid #222138"}, - "targetPosition": Position.Left, - "sourcePosition": Position.Right - }, { - "id": "preproc", - "data": {"label": "preproc", "udf": {"container": null, "builtin": {"name": "cat"}}}, - "position": {"x": 308.0009479912802, "y": 104}, - "targetPosition": Position.Left, - "sourcePosition": Position.Right - }, { - "id": "train", - "data": {"label": "train", "udf": {"container": null, "builtin": {"name": "cat"}}}, - "position": {"x": 752.0003643437127, "y": 18}, - "targetPosition": Position.Left, - "sourcePosition": Position.Right - }, { - "id": "train-1", - "data": {"label": "train-1", "udf": {"container": null, "builtin": {"name": "cat"}}}, - "position": {"x": 974.0005434138947, "y": 18}, - "targetPosition": Position.Left, - "sourcePosition": Position.Right - }, { - "id": "infer", - "data": {"label": "infer", "udf": {"container": null, "builtin": {"name": "cat"}}}, - "position": {"x": 530.0006491028594, "y": 104}, - "targetPosition": Position.Left, - "sourcePosition": Position.Right - }, { - "id": "postproc", - "data": {"label": "postproc", "udf": {"container": null, "builtin": {"name": "cat"}}}, - "position": {"x": 752.0008060721026, "y": 147}, - "targetPosition": Position.Left, - "sourcePosition": Position.Right - }, { - "id": "log-output", - "data": {"label": "log-output", "sink": {"log": {}}}, - "position": {"x": 974.0001823872657, "y": 104}, - "type": "output", - "style": {"background": "#D6D5E6", "color": "#333", "border": "1px solid #222138"}, - "targetPosition": Position.Left, - "sourcePosition": Position.Right - }, { - "id": "train-output", - "data": {"label": "train-output", "sink": {"log": {}}}, - "position": {"x": 1196.0000658161566, "y": 18}, - "type": "output", - "style": {"background": "#D6D5E6", "color": "#333", "border": "1px solid #222138"}, - "targetPosition": Position.Left, - "sourcePosition": Position.Right - }, { - "id": "publisher", - "data": {"label": "publisher", "sink": {"log": {}}}, - "position": {"x": 974.0002039119054, "y": 190}, - "type": "output", - "style": {"background": "#D6D5E6", "color": "#333", "border": "1px solid #222138"}, - "targetPosition": Position.Left, - "sourcePosition": Position.Right - }], - "edges": [{ - "id": "input-preproc", - "label": "0", - "source": "input", - "target": "preproc", - "data": { - "pipeline": "simple-pipeline", - "fromVertex": "input", - "toVertex": "preproc", - "bufferName": "numaflow-system-simple-pipeline-postproc-publisher", - "pendingCount": 0, - "ackPendingCount": 0, - "totalMessages": 0, - "bufferLength": 10000, - "bufferUsageLimit": 0.8, - "bufferUsage": 0, - "isFull": false, - "conditions": null, - "label": "0" - }, - "animated": true, - "type": "smoothstep" - }, { - "id": "preproc-infer", - "label": "0", - "source": "preproc", - "target": "infer", - "data": { - "pipeline": "simple-pipeline", - "fromVertex": "preproc", - "toVertex": "infer", - "bufferName": "numaflow-system-simple-pipeline-postproc-publisher", - "pendingCount": 0, - "ackPendingCount": 0, - "totalMessages": 0, - "bufferLength": 10000, - "bufferUsageLimit": 0.8, - "bufferUsage": 0, - "isFull": false, - "conditions": null, - "label": "0" - }, - "animated": true, - "type": "smoothstep" - }, { - "id": "infer-train", - "label": "0", - "source": "infer", - "target": "train", - "data": { - "pipeline": "simple-pipeline", - "fromVertex": "infer", - "toVertex": "train", - "bufferName": "numaflow-system-simple-pipeline-postproc-publisher", - "pendingCount": 0, - "ackPendingCount": 0, - "totalMessages": 18, - "bufferLength": 10000, - "bufferUsageLimit": 0.8, - "bufferUsage": 0, - "isFull": false, - "conditions": {"keyIn": ["train"]}, - "label": "0" - }, - "animated": true, - "type": "smoothstep" - }, { - "id": "train-train-1", - "label": "49", - "source": "train", - "target": "train-1", - "data": { - "pipeline": "simple-pipeline", - "fromVertex": "train", - "toVertex": "train-1", - "bufferName": "numaflow-system-simple-pipeline-postproc-publisher", - "pendingCount": 19, - "ackPendingCount": 30, - "totalMessages": 49, - "bufferLength": 10000, - "bufferUsageLimit": 0.8, - "bufferUsage": 0.0049, - "isFull": false, - "conditions": null, - "label": "49" - }, - "animated": true, - "type": "smoothstep" - }, { - "id": "train-1-train-output", - "label": "0", - "source": "train-1", - "target": "train-output", - "data": { - "pipeline": "simple-pipeline", - "fromVertex": "train-1", - "toVertex": "train-output", - "bufferName": "numaflow-system-simple-pipeline-postproc-publisher", - "pendingCount": 0, - "ackPendingCount": 0, - "totalMessages": 30, - "bufferLength": 10000, - "bufferUsageLimit": 0.8, - "bufferUsage": 0, - "isFull": false, - "conditions": null, - "label": "0" - }, - "animated": true, - "type": "smoothstep" - }, { - "id": "infer-postproc", - "label": "53", - "source": "infer", - "target": "postproc", - "data": { - "pipeline": "simple-pipeline", - "fromVertex": "infer", - "toVertex": "postproc", - "bufferName": "numaflow-system-simple-pipeline-postproc-publisher", - "pendingCount": 0, - "ackPendingCount": 53, - "totalMessages": 53, - "bufferLength": 10000, - "bufferUsageLimit": 0.8, - "bufferUsage": 0.0053, - "isFull": false, - "conditions": {"keyIn": ["postproc"]}, - "label": "53" - }, - "animated": true, - "type": "smoothstep" - }, { - "id": "postproc-log-output", - "label": "0", - "source": "postproc", - "target": "log-output", - "data": { - "pipeline": "simple-pipeline", - "fromVertex": "postproc", - "toVertex": "log-output", - "bufferName": "numaflow-system-simple-pipeline-postproc-publisher", - "pendingCount": 0, - "ackPendingCount": 0, - "totalMessages": 0, - "bufferLength": 10000, - "bufferUsageLimit": 0.8, - "bufferUsage": 0, - "isFull": false, - "conditions": null, - "label": "0" - }, - "animated": true, - "type": "smoothstep" - }, { - "id": "postproc-publisher", - "label": "0", - "source": "postproc", - "target": "publisher", - "data": { - "pipeline": "simple-pipeline", - "fromVertex": "postproc", - "toVertex": "publisher", - "bufferName": "numaflow-system-simple-pipeline-postproc-publisher", - "pendingCount": 0, - "ackPendingCount": 0, - "totalMessages": 49, - "bufferLength": 10000, - "bufferUsageLimit": 0.8, - "bufferUsage": 0, - "isFull": false, - "conditions": null, - "label": "0" - }, - "animated": true, - "type": "smoothstep" - }], - "status": { - "conditions": [{ - "type": "Configured", - "status": "True", - "lastTransitionTime": "2022-05-19T21:06:02Z", - "reason": "Successful", - "message": "Successful" - }, { - "type": "Deployed", - "status": "True", - "lastTransitionTime": "2022-05-19T21:06:02Z", - "reason": "Successful", - "message": "Successful" - }], - "phase": "Running", - "lastUpdated": "2022-05-19T21:06:02Z" - - } - } - - it("Load Graph screen", async () => { - render() - await waitFor(() => expect(screen.getByTestId("graph")).toBeInTheDocument()); - await waitFor(() => expect(screen.getByTestId("graph")).toBeVisible()); - await waitFor(() => expect(screen.getByTestId("card")).toBeVisible()); - - }) - -}) + it("Load Graph screen", async () => { + render( + + ); + await waitFor(() => + expect(screen.getByTestId("graph")).toBeInTheDocument() + ); + await waitFor(() => expect(screen.getByTestId("graph")).toBeVisible()); + await waitFor(() => expect(screen.getByTestId("card")).toBeVisible()); + }); +}); diff --git a/ui/src/components/pipeline/graph/Graph.tsx b/ui/src/components/pipeline/graph/Graph.tsx index 0d748010d..2ed94170e 100644 --- a/ui/src/components/pipeline/graph/Graph.tsx +++ b/ui/src/components/pipeline/graph/Graph.tsx @@ -8,20 +8,29 @@ import ReactFlow, { EdgeChange, Node, NodeChange, + NodeTypes, Position, } from "react-flow-renderer"; import dagre from "dagre"; import EdgeInfo from "../edgeinfo/EdgeInfo"; import NodeInfo from "../nodeinfo/NodeInfo"; import { GraphData } from "../../../utils/models/pipeline"; - import "./Graph.css"; import Spec from "../spec/Spec"; import { Card } from "@mui/material"; +import SourceNode from "./SourceNode"; +import UDFNode from "./UDFNode"; +import SinkNode from "./SinkNode"; const nodeWidth = 172; const nodeHeight = 36; +const defaultNodeTypes: NodeTypes = { + udf: UDFNode, + sink: SinkNode, + source: SourceNode, +}; + const getLayoutedElements = ( nodes: Node[], edges: Edge[], @@ -156,6 +165,7 @@ export default function Graph(props: GraphProps) { > ); -} \ No newline at end of file +} diff --git a/ui/src/components/pipeline/graph/Node.css b/ui/src/components/pipeline/graph/Node.css new file mode 100644 index 000000000..f9bb045ed --- /dev/null +++ b/ui/src/components/pipeline/graph/Node.css @@ -0,0 +1,20 @@ +.node-rate { + width: 40px; + height: 10px; + background: #ccd6dd; + border: 1px solid #8fa4b1; + position: absolute; + bottom: -5px; + right: 10px; + border-radius: 5px; + padding: 0 5px; + font-size: 0.8em; + text-transform: lowercase; + margin-right: 1px; + text-align: center; + font-family: "IBM Plex Sans", sans-serif; +} + +.node-tooltip { + font-size: 1.2em; +} \ No newline at end of file diff --git a/ui/src/components/pipeline/graph/SinkNode.tsx b/ui/src/components/pipeline/graph/SinkNode.tsx new file mode 100644 index 000000000..ae3a85d6e --- /dev/null +++ b/ui/src/components/pipeline/graph/SinkNode.tsx @@ -0,0 +1,50 @@ +import { memo } from "react"; +import { Handle, NodeProps, Position } from "react-flow-renderer"; +import { Tooltip } from "@mui/material"; +import "./Node.css"; + +const SinkNode = ({ + data, + isConnectable, + targetPosition = Position.Top, +}: NodeProps) => { + return ( +
+
+ +
Processing Rates
+
1 min: {data?.rate?.ratePerMin}
+
5 min: {data?.rate?.ratePerFiveMin}
+
15 min: {data?.rate?.ratePerFifteenMin}
+
+ } + arrow + > +
{data?.rate?.ratePerMin}/min
+ + + {data?.label} +
+ + ); +}; + +export default memo(SinkNode); diff --git a/ui/src/components/pipeline/graph/SourceNode.tsx b/ui/src/components/pipeline/graph/SourceNode.tsx new file mode 100644 index 000000000..0f595d24d --- /dev/null +++ b/ui/src/components/pipeline/graph/SourceNode.tsx @@ -0,0 +1,50 @@ +import { memo } from "react"; +import { Handle, NodeProps, Position } from "react-flow-renderer"; +import { Tooltip } from "@mui/material"; +import "./Node.css"; + +const SourceNode = ({ + data, + isConnectable, + sourcePosition = Position.Bottom, +}: NodeProps) => { + return ( +
+
+ +
Processing Rates
+
1 min: {data?.rate?.ratePerMin}
+
5 min: {data?.rate?.ratePerFiveMin}
+
15 min: {data?.rate?.ratePerFifteenMin}
+
+ } + arrow + > +
{data?.rate?.ratePerMin}/min
+ + + {data?.label} + +
+ + ); +}; +export default memo(SourceNode); diff --git a/ui/src/components/pipeline/graph/UDFNode.tsx b/ui/src/components/pipeline/graph/UDFNode.tsx new file mode 100644 index 000000000..9d79b6684 --- /dev/null +++ b/ui/src/components/pipeline/graph/UDFNode.tsx @@ -0,0 +1,56 @@ +import { memo } from "react"; +import { Handle, NodeProps, Position } from "react-flow-renderer"; +import { Tooltip } from "@mui/material"; +import "./Node.css"; + +const UDFNode = ({ + data, + isConnectable, + targetPosition = Position.Top, + sourcePosition = Position.Bottom, +}: NodeProps) => { + return ( +
+
+ +
Processing Rates
+
1 min: {data?.rate?.ratePerMin}
+
5 min: {data?.rate?.ratePerFiveMin}
+
15 min: {data?.rate?.ratePerFifteenMin}
+
+ } + arrow + > +
{data?.rate?.ratePerMin}/min
+ + + {data?.label} + +
+ + ); +}; + +export default memo(UDFNode); diff --git a/ui/src/utils/models/pipeline.ts b/ui/src/utils/models/pipeline.ts index 5fac04789..867c4a710 100644 --- a/ui/src/utils/models/pipeline.ts +++ b/ui/src/utils/models/pipeline.ts @@ -1,5 +1,11 @@ import { Node, Edge } from "react-flow-renderer"; +export interface ProcessingRates { + ratePerMin: number; + ratePerFiveMin: number; + ratePerFifteenMin: number; +} + export interface EdgeInfo { fromVertex: string; toVertex: string;