diff --git a/ui/src/components/common/Routes/index.tsx b/ui/src/components/common/Routes/index.tsx index ec6f22be67..d52f24c574 100644 --- a/ui/src/components/common/Routes/index.tsx +++ b/ui/src/components/common/Routes/index.tsx @@ -2,6 +2,7 @@ import { useLocation } from "react-router-dom"; import { Cluster } from "../../pages/Cluster"; import { Namespaces } from "../../pages/Namespace"; import { Pipeline } from "../../pages/Pipeline"; +import { MonoVertex } from "../../pages/MonoVertex"; export interface RoutesProps { managedNamespace?: string; @@ -12,11 +13,14 @@ export function Routes(props: RoutesProps) { const query = new URLSearchParams(location.search); const ns = query.get("namespace") || ""; const pl = query.get("pipeline") || ""; + const type = query.get("type") || ""; const { managedNamespace } = props; if (managedNamespace) { - return pl ? ( + return type ? ( + + ) : pl ? ( ) : ( @@ -25,6 +29,8 @@ export function Routes(props: RoutesProps) { if (ns === "" && pl === "") { return ; + } else if (type !== "") { + return ; } else if (pl !== "") { return ; } else { diff --git a/ui/src/components/common/SlidingSidebar/partials/VertexDetails/index.tsx b/ui/src/components/common/SlidingSidebar/partials/VertexDetails/index.tsx index 5d18a79eb3..6113140894 100644 --- a/ui/src/components/common/SlidingSidebar/partials/VertexDetails/index.tsx +++ b/ui/src/components/common/SlidingSidebar/partials/VertexDetails/index.tsx @@ -12,7 +12,8 @@ import { CloseModal } from "../CloseModal"; import sourceIcon from "../../../../../images/source.png"; import sinkIcon from "../../../../../images/sink.png"; import mapIcon from "../../../../../images/map.png"; -import reducIcon from "../../../../../images/reduce.png"; +import reduceIcon from "../../../../../images/reduce.png"; +import monoVertexIcon from "../../../../../images/monoVertex.svg"; import "./style.css"; @@ -27,6 +28,7 @@ export enum VertexType { SINK, MAP, REDUCE, + MONOVERTEX, } export interface VertexDetailsProps { @@ -71,6 +73,8 @@ export function VertexDetails({ setVertexType(VertexType.MAP); } else if (type === "sink") { setVertexType(VertexType.SINK); + } else if (type === "monoVertex") { + setVertexType(VertexType.MONOVERTEX); } setVertexSpec(vertexSpecs); }, [vertexSpecs, type]); @@ -98,7 +102,7 @@ export function VertexDetails({ return ( reduce vertex @@ -127,6 +131,17 @@ export function VertexDetails({ Sink Vertex ); + case VertexType.MONOVERTEX: + return ( + + mono vertex + Mono Vertex + + ); default: return ( @@ -246,6 +261,7 @@ export function VertexDetails({ namespaceId={namespaceId} pipelineId={pipelineId} vertexId={vertexId} + type={type} /> )} @@ -261,6 +277,7 @@ export function VertexDetails({ pipelineId={pipelineId} vertexId={vertexId} vertexSpec={vertexSpec} + type={type} setModalOnClose={handleUpdateModalClose} refresh={refresh} /> @@ -276,6 +293,7 @@ export function VertexDetails({ )} @@ -288,8 +306,10 @@ export function VertexDetails({ {tabValue === K8S_EVENTS_TAB_INDEX && ( diff --git a/ui/src/components/common/SlidingSidebar/partials/VertexDetails/partials/ProcessingRates/index.tsx b/ui/src/components/common/SlidingSidebar/partials/VertexDetails/partials/ProcessingRates/index.tsx index 8bcd790234..87b7c0574f 100644 --- a/ui/src/components/common/SlidingSidebar/partials/VertexDetails/partials/ProcessingRates/index.tsx +++ b/ui/src/components/common/SlidingSidebar/partials/VertexDetails/partials/ProcessingRates/index.tsx @@ -13,12 +13,14 @@ import "./style.css"; export interface ProcessingRatesProps { vertexId: string; pipelineId: string; + type: string; vertexMetrics: any; } export function ProcessingRates({ vertexMetrics, pipelineId, + type, vertexId, }: ProcessingRatesProps) { const [foundRates, setFoundRates] = useState([]); @@ -27,13 +29,15 @@ export function ProcessingRates({ if (!vertexMetrics || !pipelineId || !vertexId) { return; } - const vertexData = vertexMetrics[vertexId]; + const vertexData = + type === "monoVertex" ? [vertexMetrics] : vertexMetrics[vertexId]; if (!vertexData) { return; } const rates: PipelineVertexMetric[] = []; vertexData.forEach((item: any, index: number) => { - if (item.pipeline !== pipelineId || !item.processingRates) { + const key = type === "monoVertex" ? "monoVertex" : "pipeline"; + if (item?.[key] !== pipelineId || !item.processingRates) { return; // continue } rates.push({ @@ -64,7 +68,7 @@ export function ProcessingRates({ - Partition + {type !== "monoVertex" && Partition} 1m 5m 15m @@ -81,7 +85,9 @@ export function ProcessingRates({ {!!foundRates.length && foundRates.map((metric) => ( - {metric.partition} + {type !== "monoVertex" && ( + {metric.partition} + )} {metric.oneM}/sec {metric.fiveM}/sec {metric.fifteenM}/sec diff --git a/ui/src/components/common/SlidingSidebar/partials/VertexDetails/partials/VertexUpdate/index.tsx b/ui/src/components/common/SlidingSidebar/partials/VertexDetails/partials/VertexUpdate/index.tsx index 1163263cbc..a8b836a127 100644 --- a/ui/src/components/common/SlidingSidebar/partials/VertexDetails/partials/VertexUpdate/index.tsx +++ b/ui/src/components/common/SlidingSidebar/partials/VertexDetails/partials/VertexUpdate/index.tsx @@ -20,6 +20,7 @@ export interface VertexUpdateProps { pipelineId: string; vertexId: string; vertexSpec: any; + type: string; setModalOnClose?: (props: SpecEditorModalProps | undefined) => void; refresh: () => void; } @@ -30,6 +31,7 @@ export function VertexUpdate({ vertexId, vertexSpec, setModalOnClose, + type: vertexType, refresh, }: VertexUpdateProps) { const [loading, setLoading] = useState(false); @@ -225,7 +227,11 @@ export function VertexUpdate({ > >( + new Map() +); + +// TODO add health status + processing rate once implemented +export function MonoVertex({ namespaceId: nsIdProp }: MonoVertexProps) { + const location = useLocation(); + const query = new URLSearchParams(location.search); + const pipelineId = query.get("pipeline") || ""; + const nsIdParam = query.get("namespace") || ""; + const namespaceId = nsIdProp || nsIdParam; + const { addError, setSidebarProps } = useContext(AppContext); + const { + data, + loading: summaryLoading, + error, + refresh: summaryRefresh, + } = useMonoVertexSummaryFetch({ namespaceId, pipelineId, addError }); + + const { + pipeline, + vertices, + pipelineErr, + loading, + refresh: graphRefresh, + } = useMonoVertexViewFetch(namespaceId, pipelineId, addError); + + const refresh = useCallback(() => { + graphRefresh(); + summaryRefresh(); + }, [graphRefresh, summaryRefresh]); + + const handleK8sEventsClick = useCallback(() => { + if (!namespaceId || !pipelineId || !setSidebarProps) { + return; + } + const vertexMap = new Map(); + setSidebarProps({ + type: SidebarType.NAMESPACE_K8s, + k8sEventsProps: { + namespaceId, + pipelineId: `${pipelineId} (MonoVertex)`, + headerText: "Pipeline K8s Events", + vertexFilterOptions: vertexMap, + }, + }); + }, [namespaceId, pipelineId, setSidebarProps, vertices]); + + const summarySections: SummarySection[] = useMemo(() => { + if (summaryLoading) { + return [ + { + type: SummarySectionType.CUSTOM, + customComponent: ( + + ), + }, + ]; + } + if (error) { + return [ + { + type: SummarySectionType.CUSTOM, + customComponent: ( + + ), + }, + ]; + } + if (!data) { + return []; + } + const pipelineData = data?.monoVertexData; + const pipelineStatus = pipelineData?.monoVertex?.status?.phase || UNKNOWN; + return [ + // pipeline collection + { + type: SummarySectionType.CUSTOM, + customComponent: ( + + ), + }, + { + type: SummarySectionType.CUSTOM, + customComponent: ( + + ), + }, + { + type: SummarySectionType.CUSTOM, + customComponent: ( +
+ K8s Events +
+ ), + }, + ]; + }, [summaryLoading, error, data, pipelineId, refresh]); + + const content = useMemo(() => { + if (pipelineErr) { + return ( + + + + + + ); + } + if (loading) { + return ( + + + + ); + } + return ( + + + + ); + }, [ + pipelineErr, + loading, + vertices, + pipeline, + namespaceId, + pipelineId, + refresh, + ]); + + return ( + + {content} + + } + /> + ); +} diff --git a/ui/src/components/pages/MonoVertex/partials/MonoVertexStatus/index.tsx b/ui/src/components/pages/MonoVertex/partials/MonoVertexStatus/index.tsx new file mode 100644 index 0000000000..04ebb4621a --- /dev/null +++ b/ui/src/components/pages/MonoVertex/partials/MonoVertexStatus/index.tsx @@ -0,0 +1,58 @@ +import React from "react"; +import Box from "@mui/material/Box"; +import { IconsStatusMap, StatusString } from "../../../../../utils"; + +import "./style.css"; + +export interface MonoVertexStatusProps { + status: any; +} + +export function MonoVertexStatus({ status }: MonoVertexStatusProps) { + return ( + + + STATUS + + + + {status} + {"healthy"} + + + {StatusString[status]} + + + {StatusString["healthy"]} + + + + + + + ); +} diff --git a/ui/src/components/pages/MonoVertex/partials/MonoVertexStatus/style.css b/ui/src/components/pages/MonoVertex/partials/MonoVertexStatus/style.css new file mode 100644 index 0000000000..ef1f338503 --- /dev/null +++ b/ui/src/components/pages/MonoVertex/partials/MonoVertexStatus/style.css @@ -0,0 +1,11 @@ +.pipeline-logo { + width: 2.4rem; +} + +.pipeline-logo-text { + color: #3C4348; + font-size: 1.4rem; + font-style: normal; + font-weight: 400; + margin-top: 0.2rem; +} diff --git a/ui/src/components/pages/MonoVertex/partials/MonoVertexSummaryStatus/index.tsx b/ui/src/components/pages/MonoVertex/partials/MonoVertexSummaryStatus/index.tsx new file mode 100644 index 0000000000..83b570d188 --- /dev/null +++ b/ui/src/components/pages/MonoVertex/partials/MonoVertexSummaryStatus/index.tsx @@ -0,0 +1,135 @@ +import React, { useCallback, useContext } from "react"; +import Box from "@mui/material/Box"; +import { useLocation } from "react-router-dom"; +import { SidebarType } from "../../../../common/SlidingSidebar"; +import { AppContextProps } from "../../../../../types/declarations/app"; +import { AppContext } from "../../../../../App"; +import { ViewType } from "../../../../common/SpecEditor"; + +import "./style.css"; + +export interface MonoVertexSummaryProps { + pipelineId: any; + pipeline: any; + refresh: () => void; +} + +export function MonoVertexSummaryStatus({ + pipelineId, + pipeline, + refresh, +}: MonoVertexSummaryProps) { + const location = useLocation(); + const query = new URLSearchParams(location.search); + const namespaceId = query.get("namespace") || ""; + const { setSidebarProps } = useContext(AppContext); + + const handleUpdateComplete = useCallback(() => { + refresh(); + if (!setSidebarProps) { + return; + } + // Close sidebar + setSidebarProps(undefined); + }, [setSidebarProps, refresh]); + + const handleSpecClick = useCallback(() => { + if (!namespaceId || !setSidebarProps) { + return; + } + setSidebarProps({ + type: SidebarType.PIPELINE_UPDATE, + specEditorProps: { + titleOverride: `View Pipeline: ${pipelineId}`, + initialYaml: pipeline, + namespaceId, + pipelineId, + viewType: ViewType.READ_ONLY, + // viewType: isReadOnly ? ViewType.READ_ONLY : ViewType.TOGGLE_EDIT, + onUpdateComplete: handleUpdateComplete, + }, + }); + }, [ + namespaceId, + pipelineId, + setSidebarProps, + pipeline, + handleUpdateComplete, + ]); + + return ( + + + SUMMARY + + +
+ Created On: +
+
+ + Last Updated On:{" "} + +
+ {/*
*/} + {/* Last Refresh: */} + {/* 2023-12-07T02:02:00Z*/} + {/*
*/} +
+ +
+ {pipeline?.metadata?.creationTimestamp} +
+
+ {pipeline?.status?.lastUpdated} +
+ {/*
*/} + {/* 2023-12-07T02:02:00Z*/} + {/*
*/} +
+ +
+ +
+ {`View Specs`} +
+
+
+
+
+
+
+ ); +} diff --git a/ui/src/components/pages/MonoVertex/partials/MonoVertexSummaryStatus/style.css b/ui/src/components/pages/MonoVertex/partials/MonoVertexSummaryStatus/style.css new file mode 100644 index 0000000000..eeaffa9d73 --- /dev/null +++ b/ui/src/components/pages/MonoVertex/partials/MonoVertexSummaryStatus/style.css @@ -0,0 +1,20 @@ +.pipeline-summary-text { + color: #393A3D; + font-size: 1.4rem; + font-style: normal; + font-weight: 400; + line-height: 2.35rem; +} + +.pipeline-summary-subtitle { + font-weight: 600; +} + +.pipeline-onclick-events { + color: #393A3D; + font-size: 1.4rem; + font-style: normal; + font-weight: 600; + text-decoration: underline; + cursor: pointer; +} diff --git a/ui/src/components/pages/MonoVertex/style.css b/ui/src/components/pages/MonoVertex/style.css new file mode 100644 index 0000000000..abd662f23a --- /dev/null +++ b/ui/src/components/pages/MonoVertex/style.css @@ -0,0 +1,14 @@ +.react-flow__edge-textwrapper { + cursor: pointer; +} + +.react-flow__edge-textbg { + fill: #fafafa; +} + +.pipeline-status-title { + font-weight: 600; + color: #393A3D; + font-size: 1.3rem; + font-style: normal; +} diff --git a/ui/src/components/pages/Namespace/index.tsx b/ui/src/components/pages/Namespace/index.tsx index 07d38682f5..a6fe5a1284 100644 --- a/ui/src/components/pages/Namespace/index.tsx +++ b/ui/src/components/pages/Namespace/index.tsx @@ -50,12 +50,19 @@ export function Namespaces({ namespaceId: nsIdProp }: NamespaceProps) { const nsIdParam = query.get("namespace") || ""; const namespaceId = nsIdProp || nsIdParam; const { setSidebarProps, addError } = useContext(AppContext); - const { data, pipelineRawData, isbRawData, loading, error, refresh } = - useNamespaceSummaryFetch({ - namespace: namespaceId || "", - loadOnRefresh: false, - addError, - }); + const { + data, + pipelineRawData, + isbRawData, + monoVertexRawData, + loading, + error, + refresh, + } = useNamespaceSummaryFetch({ + namespace: namespaceId || "", + loadOnRefresh: false, + addError, + }); const handleK8sEventsClick = useCallback(() => { if (!namespaceId || !setSidebarProps) { @@ -75,6 +82,11 @@ export function Namespaces({ namespaceId: nsIdProp }: NamespaceProps) { }); }); } + if (monoVertexRawData) { + Object.keys(monoVertexRawData).forEach((pipelineId) => { + pipelines.push(`${pipelineId} (MonoVertex)`); + }); + } setSidebarProps({ type: SidebarType.NAMESPACE_K8s, k8sEventsProps: { @@ -83,7 +95,7 @@ export function Namespaces({ namespaceId: nsIdProp }: NamespaceProps) { vertexFilterOptions: vertexMap, }, }); - }, [namespaceId, setSidebarProps, pipelineRawData]); + }, [namespaceId, setSidebarProps, pipelineRawData, monoVertexRawData]); const defaultPipelinesData = useMemo(() => { return [ @@ -280,6 +292,7 @@ export function Namespaces({ namespaceId: nsIdProp }: NamespaceProps) { data={data ? data : defaultNamespaceSummaryData} pipelineData={pipelineRawData} isbData={isbRawData} + monoVertexData={monoVertexRawData} refresh={refresh} /> ); diff --git a/ui/src/components/pages/Namespace/partials/MonoVertexCard/index.tsx b/ui/src/components/pages/Namespace/partials/MonoVertexCard/index.tsx new file mode 100644 index 0000000000..80cd548716 --- /dev/null +++ b/ui/src/components/pages/Namespace/partials/MonoVertexCard/index.tsx @@ -0,0 +1,580 @@ +import React, { useCallback, useContext, useEffect, useState } from "react"; +import Paper from "@mui/material/Paper"; +import { Link } from "react-router-dom"; +import { PipelineCardProps } from "../../../../../types/declarations/namespace"; +import { + Box, + Button, + CircularProgress, + Grid, + MenuItem, + Select, + SelectChangeEvent, +} from "@mui/material"; +import ArrowForwardIcon from "@mui/icons-material/ArrowForward"; +import { DeleteModal } from "../DeleteModal"; +import { + getAPIResponseError, + IconsStatusMap, + StatusString, + timeAgo, + UNKNOWN, + PAUSED, + RUNNING, + // PAUSING, + DELETING, + getBaseHref, +} from "../../../../../utils"; +import { useMonoVertexUpdateFetch } from "../../../../../utils/fetchWrappers/monoVertexUpdateFetch"; +import { AppContextProps } from "../../../../../types/declarations/app"; +import { AppContext } from "../../../../../App"; +import { SidebarType } from "../../../../common/SlidingSidebar"; +import { ViewType } from "../../../../common/SpecEditor"; +import pipelineIcon from "../../../../../images/pipeline.png"; + +import "./style.css"; + +export interface DeleteProps { + type: "pipeline"; + pipelineId?: string; +} + +export function MonoVertexCard({ + namespace, + data, + statusData, + refresh, +}: PipelineCardProps) { + const { setSidebarProps, host, isReadOnly } = + useContext(AppContext); + const [viewOption] = useState("view"); + const [editOption] = useState("edit"); + const [deleteOption] = useState("delete"); + const [deleteProps, setDeleteProps] = useState(); + const [statusPayload, setStatusPayload] = useState(undefined); + const [error, setError] = useState(undefined); + const [successMessage, setSuccessMessage] = useState( + undefined + ); + const [timerDateStamp, setTimerDateStamp] = useState(undefined); + const [timer, setTimer] = useState(undefined); + const [pipelineAbleToLoad, setPipelineAbleToLoad] = useState(false); + const { pipelineAvailable } = useMonoVertexUpdateFetch({ + namespaceId: namespace, + pipelineId: data?.name, + active: !pipelineAbleToLoad, + refreshInterval: 5000, // 5 seconds + }); + + useEffect(() => { + if (pipelineAvailable) { + setPipelineAbleToLoad(true); + } + }, [pipelineAvailable]); + + const handleUpdateComplete = useCallback(() => { + refresh(); + setPipelineAbleToLoad(false); + if (!setSidebarProps) { + return; + } + // Close sidebar + setSidebarProps(undefined); + }, [setSidebarProps, refresh]); + + const handleViewChange = useCallback( + (event: SelectChangeEvent) => { + if (event.target.value === "pipeline" && setSidebarProps) { + setSidebarProps({ + type: SidebarType.PIPELINE_UPDATE, + specEditorProps: { + titleOverride: `View Pipeline: ${data?.name}`, + initialYaml: statusData?.monoVertex, + namespaceId: namespace, + pipelineId: data?.name, + viewType: ViewType.READ_ONLY, + onUpdateComplete: handleUpdateComplete, + }, + }); + } + }, + [setSidebarProps, handleUpdateComplete, data] + ); + + const handleEditChange = useCallback( + (event: SelectChangeEvent) => { + if (event.target.value === "pipeline" && setSidebarProps) { + setSidebarProps({ + type: SidebarType.PIPELINE_UPDATE, + specEditorProps: { + initialYaml: statusData?.monoVertex, + namespaceId: namespace, + pipelineId: data?.name, + viewType: ViewType.EDIT, + onUpdateComplete: handleUpdateComplete, + }, + }); + } + }, + [setSidebarProps, handleUpdateComplete, data] + ); + + const handleDeleteChange = useCallback( + (event: SelectChangeEvent) => { + if (event.target.value === "pipeline") { + setDeleteProps({ + type: "pipeline", + pipelineId: data?.name, + }); + } + }, + [data] + ); + + const handleDeleteComplete = useCallback(() => { + refresh(); + setDeleteProps(undefined); + }, [refresh]); + + const handeDeleteCancel = useCallback(() => { + setDeleteProps(undefined); + }, []); + + const pipelineStatus = statusData?.monoVertex?.status?.phase || UNKNOWN; + const handleTimer = useCallback(() => { + const dateString = new Date().toISOString(); + const time = timeAgo(dateString); + setTimerDateStamp(time); + const pauseTimer = setInterval(() => { + const time = timeAgo(dateString); + setTimerDateStamp(time); + }, 1000); + setTimer(pauseTimer); + }, []); + + const handlePlayClick = useCallback(() => { + handleTimer(); + setStatusPayload({ + spec: { + lifecycle: { + desiredPhase: RUNNING, + }, + }, + }); + }, []); + + const handlePauseClick = useCallback(() => { + handleTimer(); + setStatusPayload({ + spec: { + lifecycle: { + desiredPhase: PAUSED, + }, + }, + }); + }, []); + + useEffect(() => { + const patchStatus = async () => { + try { + const response = await fetch( + `${host}${getBaseHref()}/api/v1/namespaces/${namespace}/mono-vertices/${ + data?.name + }`, + { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(statusPayload), + } + ); + const error = await getAPIResponseError(response); + if (error) { + setError(error); + } else { + refresh(); + setSuccessMessage("Status updated successfully"); + } + } catch (e: any) { + setError(e); + } + }; + if (statusPayload) { + patchStatus(); + } + }, [statusPayload, host]); + + useEffect(() => { + if ( + statusPayload?.spec?.lifecycle?.desiredPhase === PAUSED && + statusData?.monoVertex?.status?.phase === PAUSED + ) { + clearInterval(timer); + setStatusPayload(undefined); + } + if ( + statusPayload?.spec?.lifecycle?.desiredPhase === RUNNING && + statusData?.monoVertex?.status?.phase === RUNNING + ) { + clearInterval(timer); + setStatusPayload(undefined); + } + }, [statusData]); + + return ( + <> + + + pipeline icon + + + {data?.name} + + + {!isReadOnly && ( + + {error && statusPayload ? ( +
+ {error} +
+ ) : successMessage && + statusPayload && + ((statusPayload.spec.lifecycle.desiredPhase === PAUSED && + statusData?.monoVertex?.status?.phase !== PAUSED) || + (statusPayload.spec.lifecycle.desiredPhase === RUNNING && + statusData?.monoVertex?.status?.phase !== RUNNING)) ? ( +
+ {" "} + + + {statusPayload?.spec?.lifecycle?.desiredPhase === PAUSED + ? "Pipeline Pausing..." + : "Pipeline Resuming..."} + + + {timerDateStamp} + + +
+ ) : ( + "" + )} + + + +
+ )} + + {pipelineAbleToLoad ? ( + + ) : ( + + )} + +
+ + + + + Status: + Health: + + + Status + Health + + + {StatusString[pipelineStatus]} + {StatusString["healthy"]} + + + + + {isReadOnly && ( + + + + )} + {!isReadOnly && ( + + + + )} + {!isReadOnly && ( + + + + )} + + + {deleteProps && ( + + )} +
+ + ); +} diff --git a/ui/src/components/pages/Namespace/partials/MonoVertexCard/style.css b/ui/src/components/pages/Namespace/partials/MonoVertexCard/style.css new file mode 100644 index 0000000000..c0ad3f86d6 --- /dev/null +++ b/ui/src/components/pages/Namespace/partials/MonoVertexCard/style.css @@ -0,0 +1,10 @@ +.pipeline-card-name { + font-size: 2rem; + font-style: normal; + font-weight: 400; + color: #000; +} + +.pipeline-card-icon { + width: 2.4rem; +} \ No newline at end of file diff --git a/ui/src/components/pages/Namespace/partials/NamespaceListingWrapper/PipelineListing/index.tsx b/ui/src/components/pages/Namespace/partials/NamespaceListingWrapper/PipelineListing/index.tsx index 3ea6280f0e..18f48cd3f0 100644 --- a/ui/src/components/pages/Namespace/partials/NamespaceListingWrapper/PipelineListing/index.tsx +++ b/ui/src/components/pages/Namespace/partials/NamespaceListingWrapper/PipelineListing/index.tsx @@ -12,12 +12,14 @@ import { UNKNOWN, } from "../../../../../../utils"; import { ListingProps } from "../ISBListing"; -import { PipelineData } from "../PipelinesTypes"; +import { MonoVertexData, PipelineData } from "../PipelinesTypes"; import { PipelineCard } from "../../PipelineCard"; +import { MonoVertexCard } from "../../MonoVertexCard"; interface PipelineListingProps extends ListingProps { pipelineData: Map | undefined; isbData: any; + monoVertexData: Map | undefined; totalCount: number; } @@ -31,10 +33,11 @@ export function PipelineListing({ totalCount, search, isbData, + monoVertexData, }: PipelineListingProps) { - const [filteredPipelines, setFilteredPipelines] = useState( - [] - ); + const [filteredPipelines, setFilteredPipelines] = useState< + (PipelineData | MonoVertexData)[] + >([]); const [page, setPage] = useState(1); const [totalPages, setTotalPages] = useState( Math.ceil(totalCount / MAX_PAGE_SIZE) @@ -72,18 +75,30 @@ export function PipelineListing({ margin: "0.8rem 0 2.4rem 0", }} > - {filteredPipelines.map((p: PipelineData) => { - const isbName = pipelineData - ? pipelineData[p.name]?.pipeline?.spec - ?.interStepBufferServiceName || DEFAULT_ISB - : DEFAULT_ISB; + {filteredPipelines.map((p: PipelineData | MonoVertexData) => { + if (p?.pipeline) { + const isbName = pipelineData + ? pipelineData[p.name]?.pipeline?.spec + ?.interStepBufferServiceName || DEFAULT_ISB + : DEFAULT_ISB; + return ( + + + + ); + } return ( - - + @@ -91,52 +106,84 @@ export function PipelineListing({ })} ); - }, [filteredPipelines, namespace, refresh]); + }, [ + filteredPipelines, + namespace, + pipelineData, + isbData, + monoVertexData, + refresh, + ]); useEffect(() => { - let filtered: PipelineData[] = Object.values( + let filtered: (PipelineData | MonoVertexData)[] = Object.values( pipelineData ? pipelineData : {} ); + filtered = [ + ...filtered, + ...Object.values(monoVertexData ? monoVertexData : {}), + ]; if (search) { // Filter by search - filtered = filtered.filter((p: PipelineData) => p.name.includes(search)); + filtered = filtered.filter((p: PipelineData | MonoVertexData) => + p.name.includes(search) + ); } // Sorting if (orderBy.value === ALPHABETICAL_SORT) { - filtered?.sort((a: PipelineData, b: PipelineData) => { - if (orderBy.sortOrder === ASC) { - return a.name > b.name ? 1 : -1; - } else { - return a.name < b.name ? 1 : -1; + filtered?.sort( + ( + a: PipelineData | MonoVertexData, + b: PipelineData | MonoVertexData + ) => { + if (orderBy.sortOrder === ASC) { + return a.name > b.name ? 1 : -1; + } else { + return a.name < b.name ? 1 : -1; + } } - }); + ); } else if (orderBy.value === LAST_UPDATED_SORT) { - filtered?.sort((a: PipelineData, b: PipelineData) => { - if (orderBy.sortOrder === ASC) { - return a?.pipeline?.status?.lastUpdated > - b?.pipeline?.status?.lastUpdated - ? 1 - : -1; - } else { - return a?.pipeline?.status?.lastUpdated < - b?.pipeline?.status?.lastUpdated - ? 1 - : -1; + filtered?.sort( + ( + a: PipelineData | MonoVertexData, + b: PipelineData | MonoVertexData + ) => { + const aType = a?.pipeline ? "pipeline" : "monoVertex"; + const bType = b?.pipeline ? "pipeline" : "monoVertex"; + if (orderBy.sortOrder === ASC) { + return Date.parse(a?.[aType]?.status?.lastUpdated) > + Date.parse(b?.[bType]?.status?.lastUpdated) + ? 1 + : -1; + } else { + return Date.parse(a?.[aType]?.status?.lastUpdated) < + Date.parse(b?.[bType]?.status?.lastUpdated) + ? 1 + : -1; + } } - }); + ); } else { - filtered?.sort((a: PipelineData, b: PipelineData) => { - if (orderBy.sortOrder === ASC) { - return Date.parse(a?.pipeline?.metadata?.creationTimestamp) > - Date.parse(b?.pipeline?.metadata?.creationTimestamp) - ? 1 - : -1; - } else { - return Date.parse(a?.pipeline?.metadata?.creationTimestamp) < - Date.parse(b?.pipeline?.metadata?.creationTimestamp) - ? 1 - : -1; + filtered?.sort( + ( + a: PipelineData | MonoVertexData, + b: PipelineData | MonoVertexData + ) => { + const aType = a?.pipeline ? "pipeline" : "monoVertex"; + const bType = b?.pipeline ? "pipeline" : "monoVertex"; + if (orderBy.sortOrder === ASC) { + return Date.parse(a?.[aType]?.metadata?.creationTimestamp) > + Date.parse(b?.[bType]?.metadata?.creationTimestamp) + ? 1 + : -1; + } else { + return Date.parse(a?.[aType]?.metadata?.creationTimestamp) < + Date.parse(b?.[bType]?.metadata?.creationTimestamp) + ? 1 + : -1; + } } - }); + ); } //Filter by health if (healthFilter !== ALL) { @@ -153,7 +200,8 @@ export function PipelineListing({ //Filter by status if (statusFilter !== ALL) { filtered = filtered.filter((p) => { - const currentStatus = p?.pipeline?.status?.phase || UNKNOWN; + const type = p?.pipeline ? "pipeline" : "monoVertex"; + const currentStatus = p?.[type]?.status?.phase || UNKNOWN; if (currentStatus.toLowerCase() === statusFilter.toLowerCase()) { return true; } else { @@ -183,6 +231,7 @@ export function PipelineListing({ page, pipelineData, isbData, + monoVertexData, orderBy, healthFilter, statusFilter, diff --git a/ui/src/components/pages/Namespace/partials/NamespaceListingWrapper/PipelinesTypes.ts b/ui/src/components/pages/Namespace/partials/NamespaceListingWrapper/PipelinesTypes.ts index e793005b9d..35bb2d4b42 100644 --- a/ui/src/components/pages/Namespace/partials/NamespaceListingWrapper/PipelinesTypes.ts +++ b/ui/src/components/pages/Namespace/partials/NamespaceListingWrapper/PipelinesTypes.ts @@ -16,6 +16,20 @@ interface Pipeline { status: Status; } +export interface MonoVertexData { + name: string; + status: string; + monoVertex: MonoVertex; +} + +interface MonoVertex { + kind: string; + apiVersion: string; + metadata: any; + spec: any; + status: any; +} + interface Status { conditions: Condition[]; phase: string; diff --git a/ui/src/components/pages/Namespace/partials/NamespaceListingWrapper/index.tsx b/ui/src/components/pages/Namespace/partials/NamespaceListingWrapper/index.tsx index 68aa0f4350..a7889a33c9 100644 --- a/ui/src/components/pages/Namespace/partials/NamespaceListingWrapper/index.tsx +++ b/ui/src/components/pages/Namespace/partials/NamespaceListingWrapper/index.tsx @@ -111,6 +111,7 @@ export function NamespaceListingWrapper({ data, pipelineData, isbData, + monoVertexData, refresh, }: NamespacePipelineListingProps) { const { setSidebarProps, isReadOnly } = @@ -433,6 +434,7 @@ export function NamespaceListingWrapper({ diff --git a/ui/src/components/pages/Pipeline/partials/Graph/index.tsx b/ui/src/components/pages/Pipeline/partials/Graph/index.tsx index a2ac1e38b2..7c5bd60160 100644 --- a/ui/src/components/pages/Pipeline/partials/Graph/index.tsx +++ b/ui/src/components/pages/Pipeline/partials/Graph/index.tsx @@ -64,6 +64,7 @@ import source from "../../../../../images/source.png"; import map from "../../../../../images/map.png"; import reduce from "../../../../../images/reduce.png"; import sink from "../../../../../images/sink.png"; +import monoVertex from "../../../../../images/monoVertex.svg"; import input from "../../../../../images/input0.svg"; import generator from "../../../../../images/generator0.svg"; @@ -161,6 +162,7 @@ const Flow = (props: FlowProps) => { refresh, namespaceId, data, + type, } = props; const onIsLockedChange = useCallback( @@ -317,7 +319,11 @@ const Flow = (props: FlowProps) => { fontSize: "1.4rem", }} onClick={handlePlayClick} - disabled={data?.pipeline?.status?.phase === RUNNING} + disabled={ + type === "monoVertex" + ? true + : data?.pipeline?.status?.phase === RUNNING + } > Resume @@ -333,8 +339,10 @@ const Flow = (props: FlowProps) => { }} onClick={handlePauseClick} disabled={ - data?.pipeline?.status?.phase === PAUSED || - data?.pipeline?.status?.phase === PAUSING + type === "monoVertex" + ? true + : data?.pipeline?.status?.phase === PAUSED || + data?.pipeline?.status?.phase === PAUSING } > Pause @@ -513,10 +521,18 @@ const Flow = (props: FlowProps) => { Legend -
- {"source"} -
Source
-
+ {type === "monoVertex" && ( +
+ {"monoVertex"} +
MonoVertex
+
+ )} + {type === "pipeline" && ( +
+ {"source"} +
Source
+
+ )} {isMap && (
{"map"} @@ -529,10 +545,12 @@ const Flow = (props: FlowProps) => {
Reduce
)} -
- {"sink"} -
Sink
-
+ {type === "pipeline" && ( +
+ {"sink"} +
Sink
+
+ )} {isSideInput && (
{"input"} @@ -569,7 +587,7 @@ const getHiddenValue = (edges: Edge[]) => { }; export default function Graph(props: GraphProps) { - const { data, namespaceId, pipelineId, refresh } = props; + const { data, namespaceId, pipelineId, type, refresh } = props; const { sidebarProps, setSidebarProps } = useContext(AppContext); @@ -833,6 +851,7 @@ export default function Graph(props: GraphProps) { refresh={refresh} namespaceId={namespaceId} data={data} + type={type} /> diff --git a/ui/src/components/pages/Pipeline/partials/Graph/partials/CustomNode/index.tsx b/ui/src/components/pages/Pipeline/partials/Graph/partials/CustomNode/index.tsx index 1acd2e61b6..64718e4f12 100644 --- a/ui/src/components/pages/Pipeline/partials/Graph/partials/CustomNode/index.tsx +++ b/ui/src/components/pages/Pipeline/partials/Graph/partials/CustomNode/index.tsx @@ -3,6 +3,7 @@ import { FC, memo, useCallback, useContext, useMemo } from "react"; import { Tooltip } from "@mui/material"; import { Handle, NodeProps, Position } from "reactflow"; +import Box from "@mui/material/Box"; import { HighlightContext } from "../../index"; import { GeneratorColorContext } from "../../../../index"; import { HighlightContextProps } from "../../../../../../../types/declarations/graph"; @@ -11,6 +12,8 @@ import source from "../../../../../../../images/source.png"; import map from "../../../../../../../images/map.png"; import reduce from "../../../../../../../images/reduce.png"; import sink from "../../../../../../../images/sink.png"; +import monoVertex from "../../../../../../../images/monoVertex.svg"; +import transformer from "../../../../../../../images/transformer.svg"; import input0 from "../../../../../../../images/input0.svg"; import input1 from "../../../../../../../images/input1.svg"; import input2 from "../../../../../../../images/input2.svg"; @@ -148,17 +151,17 @@ const CustomNode: FC = ({ if (data?.type === "sideInput") { return ( {data?.name}
} + title={{data?.name}} arrow placement={"left"} > -
+ Spec View
} + title={Spec View} arrow placement={"bottom-start"} > -
= ({ height={16} style={{ alignSelf: "center" }} /> -
+ Show Edges} + title={Show Edges} arrow placement={"bottom-start"} > -
= ({ }} > --- -
+
= ({ id="2" position={Position.Right} /> - + ); } if (data?.type === "generator") { return ( -
= ({ onClick={(e) => e.stopPropagation()} > Generator -
+ ); } @@ -284,25 +287,89 @@ const CustomNode: FC = ({ setHighlightValues({}); }, [setHidden, setHighlightValues]); + // arrow for containers in monoVertex + const arrowSvg = useMemo(() => { + return ( + + + + + + ); + }, []); + return ( -
-
+ -
{data?.name}
- + {data?.type !== "monoVertex" && ( + {data?.name} + )} + {data?.type === "monoVertex" && ( + <> + {data?.name} + + Source Container} + arrow + placement={"left"} + > + + {"source-container"} + + + {arrowSvg} + {data?.nodeInfo?.source?.transformer && ( + Transformer Container
+ } + arrow + placement={"bottom"} + > + + {"transformer-container"} + + + )} + {data?.nodeInfo?.source?.transformer && arrowSvg} + Sink Container} + arrow + placement={"right"} + > + + {"sink-container"} + + + + + )} + {data?.podnum <= 1 ? "pod" : "pods"} -
+ } placement={"top-end"} arrow > -
+ {data?.type === "source" && ( {"source-vertex"} )} @@ -313,29 +380,32 @@ const CustomNode: FC = ({ {"reduce-vertex"} )} {data?.type === "sink" && {"sink-vertex"}} + {data?.type === "monoVertex" && ( + {"monoVertex"} + )} {data?.podnum} -
+ - {/*
+ {/* {"healthy"} -
*/} + */} -
Processing Rates
-
1 min: {data?.vertexMetrics?.ratePerMin}/sec
-
5 min: {data?.vertexMetrics?.ratePerFiveMin}/sec
-
15 min: {data?.vertexMetrics?.ratePerFifteenMin}/sec
-
+ + Processing Rates + 1 min: {data?.vertexMetrics?.ratePerMin}/sec + 5 min: {data?.vertexMetrics?.ratePerFiveMin}/sec + 15 min: {data?.vertexMetrics?.ratePerFifteenMin}/sec + } arrow placement={"bottom-end"} > -
+ {data?.vertexMetrics?.ratePerMin}/sec -
+ {(data?.type === "udf" || data?.type === "sink") && ( @@ -407,7 +477,7 @@ const CustomNode: FC = ({ /> ); })} - + {data?.nodeInfo?.sideInputs?.map((input: any, idx: number) => { return ( = ({ /> ); })} - + ); }; export default memo(CustomNode); diff --git a/ui/src/components/pages/Pipeline/partials/Graph/partials/CustomNode/style.css b/ui/src/components/pages/Pipeline/partials/Graph/partials/CustomNode/style.css index f0a4a80594..9e9b094718 100644 --- a/ui/src/components/pages/Pipeline/partials/Graph/partials/CustomNode/style.css +++ b/ui/src/components/pages/Pipeline/partials/Graph/partials/CustomNode/style.css @@ -1,160 +1,193 @@ .node-rate { - display: flex; - width: 9rem; - height: 2.2rem; - border-radius: 2rem; - background: #D1DEE9; - color: #274C77; - font-family: "IBM Plex Sans", sans-serif; - font-size: 1.2rem; - font-style: normal; - font-weight: 500; - line-height: normal; - justify-content: center; - align-items: center; - position: absolute; - bottom: -12.5%; - right: 8%; - text-transform: lowercase; + display: flex; + width: 9rem; + height: 2.2rem; + border-radius: 2rem; + background: #d1dee9; + color: #274c77; + font-family: "IBM Plex Sans", sans-serif; + font-size: 1.2rem; + font-style: normal; + font-weight: 500; + line-height: normal; + justify-content: center; + align-items: center; + position: absolute; + bottom: -12.5%; + right: 8%; + text-transform: lowercase; } .node-pods { - display: flex; - width: 10rem; - height: 2.2rem; - border-radius: 2rem; - background: #D1DEE9; - color: #274C77; - font-family: "IBM Plex Sans", sans-serif; - font-size: 1.2rem; - font-style: normal; - font-weight: 700; - line-height: normal; - justify-content: space-evenly; - align-items: center; - position: absolute; - top: -13.5%; - left: 10%; - text-transform: lowercase; + display: flex; + width: 10rem; + height: 2.2rem; + border-radius: 2rem; + background: #d1dee9; + color: #274c77; + font-family: "IBM Plex Sans", sans-serif; + font-size: 1.2rem; + font-style: normal; + font-weight: 700; + line-height: normal; + justify-content: space-evenly; + align-items: center; + position: absolute; + top: -13.5%; + left: 10%; + text-transform: lowercase; } .node-pods > img { - width: 2rem; + width: 2rem; } .node-status { - width: 2.2rem; - height: 2.2rem; - border-radius: 2rem; - background: #D1DEE9; - display: flex; - justify-content: center; - align-items: center; - position: absolute; - bottom: -12.5%; - left: 8%; + width: 2.2rem; + height: 2.2rem; + border-radius: 2rem; + background: #d1dee9; + display: flex; + justify-content: center; + align-items: center; + position: absolute; + bottom: -12.5%; + left: 8%; } .node-tooltip { - font-size: 1.2rem; - font-weight: 600; - font-family: "Avenir", sans-serif; - cursor: pointer; + font-size: 1.2rem; + font-weight: 600; + font-family: "Avenir", sans-serif; + cursor: pointer; } .react-flow__node-input { - width: 25.2rem; - height: 7.8rem; - border-radius: 2rem; - border: 0.01rem solid #009EAC; - background: var(--boxes, #F8F8FB); - box-shadow: 0 2.4rem 4.8rem -0.8rem rgba(39, 76, 119, 0.16); - cursor: pointer; + width: 25.2rem; + height: 7.8rem; + border-radius: 2rem; + border: 0.01rem solid #009eac; + background: var(--boxes, #f8f8fb); + box-shadow: 0 2.4rem 4.8rem -0.8rem rgba(39, 76, 119, 0.16); + cursor: pointer; } .node-info { - display: flex; - width: 100%; - height: 100%; - flex-direction: column; - justify-content: center; - color: #274C77; - text-align: center; - font-family: "IBM Plex Sans", sans-serif; - font-size: 1.4rem; - font-style: normal; - font-weight: 500; - line-height: normal; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; + display: flex; + width: 100%; + height: 100%; + flex-direction: column; + justify-content: center; + color: #274c77; + text-align: center; + font-family: "IBM Plex Sans", sans-serif; + font-size: 1.4rem; + font-style: normal; + font-weight: 500; + line-height: normal; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.node-info-mono { + display: flex; + width: 100%; + height: 50%; + flex-direction: column; + justify-content: center; + color: #274c77; + text-align: center; + font-family: "IBM Plex Sans", sans-serif; + font-size: 1.4rem; + font-style: normal; + font-weight: 500; + line-height: normal; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.mono-vertex-img-wrapper { + height: 2rem; + width: 2rem; + border-radius: 50%; + border: 1px solid #d1dee9; + display: flex; + align-items: center; + justify-content: center; +} + +.mono-vertex-img { + height: 1rem; + width: 1rem; } .node-icon { - width: 1.144rem; - height: 1.144rem; - display: inline-block; - vertical-align: middle; - position: relative; - margin-left: 0.01rem; - margin-right: 0.01rem; - padding-bottom: 0.05rem; + width: 1.144rem; + height: 1.144rem; + display: inline-block; + vertical-align: middle; + position: relative; + margin-left: 0.01rem; + margin-right: 0.01rem; + padding-bottom: 0.05rem; } .node-podnum { - font-weight: bold; + font-weight: bold; } .react-flow__handle { - /*background : #8D9096;*/ - z-index: -1; - margin: -0.32rem; - height: 1.6rem; - width: 1.6rem; + /*background : #8D9096;*/ + z-index: -1; + margin: -0.32rem; + height: 1.6rem; + width: 1.6rem; } .sideInput_node { - display: flex; - cursor: pointer; + display: flex; + cursor: pointer; } .sideInput_node_ele { - width: 3.6rem; - height: 3rem; - background: #E0E0E0; - display: flex; - justify-content: center; - align-items: center; + width: 3.6rem; + height: 3rem; + background: #e0e0e0; + display: flex; + justify-content: center; + align-items: center; } .generator_handle { - top: 60%; - left: 82.5%; - background: none; + top: 60%; + left: 82.5%; + background: none; } .generator_node { - font-size: 1.6rem; - background: #F8F8FB; - padding: 1.6rem 2.4rem 1.6rem 2.4rem; - margin-left: -2.4rem; - border-radius: 2rem; - border: 0.1rem solid #DAE3E8; - color: #6B6C72; + font-size: 1.6rem; + background: #f8f8fb; + padding: 1.6rem 2.4rem 1.6rem 2.4rem; + margin-left: -2.4rem; + border-radius: 2rem; + border: 0.1rem solid #dae3e8; + color: #6b6c72; } .react-flow__handle-bottom { - z-index: -1; - margin: -0.32rem; - height: 1.6rem; - width: 1.6rem; - border: none; - bottom: -15%; - background: none !important; + z-index: -1; + margin: -0.32rem; + height: 1.6rem; + width: 1.6rem; + border: none; + bottom: -15%; + background: none !important; } .sideInput_handle { - bottom: -26% ; - position: absolute; - cursor: pointer; -} \ No newline at end of file + bottom: -26%; + position: absolute; + cursor: pointer; +} diff --git a/ui/src/components/pages/Pipeline/partials/Graph/partials/NodeInfo/index.tsx b/ui/src/components/pages/Pipeline/partials/Graph/partials/NodeInfo/index.tsx index 19ec973709..50182392b2 100644 --- a/ui/src/components/pages/Pipeline/partials/Graph/partials/NodeInfo/index.tsx +++ b/ui/src/components/pages/Pipeline/partials/Graph/partials/NodeInfo/index.tsx @@ -145,6 +145,7 @@ export default function NodeInfo(props: NodeInfoProps) { namespaceId={namespaceId} pipelineId={pipelineId} vertexId={node.id} + type={node?.data?.type} /> )} diff --git a/ui/src/components/pages/Pipeline/partials/Graph/partials/NodeInfo/partials/Pods/index.tsx b/ui/src/components/pages/Pipeline/partials/Graph/partials/NodeInfo/partials/Pods/index.tsx index 9d2629fb8a..92cdccd453 100644 --- a/ui/src/components/pages/Pipeline/partials/Graph/partials/NodeInfo/partials/Pods/index.tsx +++ b/ui/src/components/pages/Pipeline/partials/Graph/partials/NodeInfo/partials/Pods/index.tsx @@ -20,7 +20,7 @@ import { } from "../../../../../../../../../types/declarations/pods"; export function Pods(props: PodsProps) { - const { namespaceId, pipelineId, vertexId } = props; + const { namespaceId, pipelineId, vertexId, type } = props; if (!namespaceId || !pipelineId || !vertexId) { return ( @@ -41,6 +41,7 @@ export function Pods(props: PodsProps) { pipelineId, vertexId, selectedPod, + type, setSelectedPod, setSelectedContainer ); diff --git a/ui/src/components/plugin/NumaflowMonitorApp/App.tsx b/ui/src/components/plugin/NumaflowMonitorApp/App.tsx index 28bc5880e9..9b65030418 100644 --- a/ui/src/components/plugin/NumaflowMonitorApp/App.tsx +++ b/ui/src/components/plugin/NumaflowMonitorApp/App.tsx @@ -32,14 +32,6 @@ import "react-toastify/dist/ReactToastify.css"; const MAX_ERRORS = 6; function App(props: AppProps) { - // TODO remove, used for testing ns only installation - // const { systemInfo, error: systemInfoError } = { - // systemInfo: { - // namespaced: true, - // managedNamespace: "test", - // }, - // error: undefined, - // }; const { hostUrl = "", namespace = "" } = props; const pageRef = useRef(); const [pageWidth, setPageWidth] = useState(0); diff --git a/ui/src/components/plugin/Routes/Routes.tsx b/ui/src/components/plugin/Routes/Routes.tsx index 3584a366c9..d7a75529cb 100644 --- a/ui/src/components/plugin/Routes/Routes.tsx +++ b/ui/src/components/plugin/Routes/Routes.tsx @@ -1,6 +1,7 @@ import { useLocation } from "react-router-dom"; import { Namespaces } from "../../pages/Namespace"; import { Pipeline } from "../../pages/Pipeline"; +import { MonoVertex } from "../../pages/MonoVertex"; export interface RoutesProps { namespace: string; @@ -9,9 +10,12 @@ export function Routes(props: RoutesProps) { const location = useLocation(); const query = new URLSearchParams(location.search); const pl = query.get("pipeline") || ""; + const type = query.get("type") || ""; const { namespace } = props; - return pl ? ( + return type ? ( + + ) : pl ? ( ) : ( diff --git a/ui/src/images/monoVertex.svg b/ui/src/images/monoVertex.svg new file mode 100644 index 0000000000..99c36ab144 --- /dev/null +++ b/ui/src/images/monoVertex.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ui/src/images/transformer.svg b/ui/src/images/transformer.svg new file mode 100644 index 0000000000..5f189d9e8a --- /dev/null +++ b/ui/src/images/transformer.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/ui/src/types/declarations/graph.d.ts b/ui/src/types/declarations/graph.d.ts index 34a9571daa..bd5abe05d0 100644 --- a/ui/src/types/declarations/graph.d.ts +++ b/ui/src/types/declarations/graph.d.ts @@ -6,6 +6,7 @@ export interface GraphProps { data: GraphData; namespaceId: string | undefined; pipelineId: string | undefined; + type: "monoVertex" | "pipeline"; refresh: () => void; } @@ -37,6 +38,7 @@ export interface FlowProps { refresh: () => void; namespaceId: string | undefined; data: any; + type: string; } export interface HighlightContextProps { diff --git a/ui/src/types/declarations/namespace.d.ts b/ui/src/types/declarations/namespace.d.ts index 3c3582e14d..65473bae4f 100644 --- a/ui/src/types/declarations/namespace.d.ts +++ b/ui/src/types/declarations/namespace.d.ts @@ -28,6 +28,7 @@ export interface NamespaceSummaryFetchResult { data?: NamespaceSummaryData; pipelineRawData?: any; isbRawData?: any; + monoVertexRawData?: any; loading: boolean; error: any; refresh: () => void; @@ -50,6 +51,7 @@ export interface NamespacePipelineListingProps { data: NamespaceSummaryData; pipelineData?: Map; isbData?: any; + monoVertexData?: any; refresh: () => void; } diff --git a/ui/src/types/declarations/pipeline.d.ts b/ui/src/types/declarations/pipeline.d.ts index 31aa0de14a..09b8757387 100644 --- a/ui/src/types/declarations/pipeline.d.ts +++ b/ui/src/types/declarations/pipeline.d.ts @@ -109,6 +109,45 @@ export interface PipelineSummaryFetchResult { refresh: () => void; } +export interface MonoVertex { + spec: any; + metadata: any; + status?: any; +} + +export interface MonoVertexSpec { + replicas: number; + source: any; + sink: any; + scale: any; +} + +export interface MonoVertexMetrics { + ratePerMin: string; + ratePerFiveMin: string; + ratePerFifteenMin: string; + podMetrics: any[]; + error: boolean; +} + +export interface MonoVertexSummary { + name: string; + status: string; + lag?: number; + monoVertex: MonoVertex; +} + +export interface MonoVertexMergeSummaryData { + monoVertexData: MonoVertexSummary; +} + +export interface MonoVertexSummaryFetchResult { + data?: MonoVertexMergeSummaryData; + loading: boolean; + error: any; + refresh: () => void; +} + export interface PipelineUpdateFetchResult { pipelineAvailable: boolean; } diff --git a/ui/src/types/declarations/pods.d.ts b/ui/src/types/declarations/pods.d.ts index eabc2d8988..31d551da0f 100644 --- a/ui/src/types/declarations/pods.d.ts +++ b/ui/src/types/declarations/pods.d.ts @@ -11,6 +11,7 @@ export interface PodsProps { namespaceId: string; pipelineId: string; vertexId: string; + type: string; } export interface PodContainerSpec { diff --git a/ui/src/utils/fetchWrappers/clusterSummaryFetch.ts b/ui/src/utils/fetchWrappers/clusterSummaryFetch.ts index e7de7408a5..ea606f2b02 100644 --- a/ui/src/utils/fetchWrappers/clusterSummaryFetch.ts +++ b/ui/src/utils/fetchWrappers/clusterSummaryFetch.ts @@ -33,14 +33,22 @@ const rawDataToClusterSummary = ( rawData.forEach((ns: any) => { // Pipeline counts - const nsPipelinesHealthyCount = ns.pipelineSummary?.active?.Healthy || 0; - const nsPipelinesWarningCount = ns.pipelineSummary?.active?.Warning || 0; - const nsPipelinesCriticalCount = ns.pipelineSummary?.active?.Critical || 0; + const nsPipelinesHealthyCount = + (ns.pipelineSummary?.active?.Healthy || 0) + + (ns.monoVertexSummary?.active?.Healthy || 0); + const nsPipelinesWarningCount = + (ns.pipelineSummary?.active?.Warning || 0) + + (ns.monoVertexSummary?.active?.Warning || 0); + const nsPipelinesCriticalCount = + (ns.pipelineSummary?.active?.Critical || 0) + + (ns.monoVertexSummary?.active?.Critical || 0); const nsPipelinesActiveCount = nsPipelinesHealthyCount + nsPipelinesWarningCount + nsPipelinesCriticalCount; - const nsPipelinesInactiveCount = ns.pipelineSummary?.inactive || 0; + const nsPipelinesInactiveCount = + (ns.pipelineSummary?.inactive || 0) + + (ns.monoVertexSummary?.inactive || 0); const nsPipelinesCount = nsPipelinesActiveCount + nsPipelinesInactiveCount; // ISB counts const nsIsbsHealthyCount = ns.isbServiceSummary?.active?.Healthy || 0; diff --git a/ui/src/utils/fetchWrappers/monoVertexFetch.ts b/ui/src/utils/fetchWrappers/monoVertexFetch.ts new file mode 100644 index 0000000000..41c0972c4a --- /dev/null +++ b/ui/src/utils/fetchWrappers/monoVertexFetch.ts @@ -0,0 +1,119 @@ +import { useEffect, useState, useCallback, useContext } from "react"; +import { Options, useFetch } from "./fetch"; +import { MonoVertexSummaryFetchResult } from "../../types/declarations/pipeline"; +import { getBaseHref } from "../index"; +import { AppContextProps } from "../../types/declarations/app"; +import { AppContext } from "../../App"; + +const DATA_REFRESH_INTERVAL = 15000; // ms + +// fetch monoVertex summary +export const useMonoVertexSummaryFetch = ({ + namespaceId, + pipelineId, + addError, +}: any) => { + const [options, setOptions] = useState({ + skip: false, + requestKey: "", + }); + + const refresh = useCallback(() => { + setOptions({ + skip: false, + requestKey: "id" + Math.random().toString(16).slice(2), + }); + }, []); + + const [results, setResults] = useState({ + data: undefined, + loading: true, + error: undefined, + refresh, + }); + + const { host } = useContext(AppContext); + + const { + data: monoVertexData, + loading: monoVertexLoading, + error: monoVertexError, + } = useFetch( + `${host}${getBaseHref()}/api/v1/namespaces/${namespaceId}/mono-vertices/${pipelineId}`, + undefined, + options + ); + + useEffect(() => { + setInterval(() => { + setOptions({ + skip: false, + requestKey: "id" + Math.random().toString(16).slice(2), + }); + }, DATA_REFRESH_INTERVAL); + }, []); + + useEffect(() => { + if (monoVertexLoading) { + if (options?.requestKey === "") { + setResults({ + data: undefined, + loading: true, + error: undefined, + refresh, + }); + } + return; + } + if (monoVertexError) { + if (options?.requestKey === "") { + // Failed on first load, return error + setResults({ + data: undefined, + loading: false, + error: monoVertexError, + refresh, + }); + } else { + // Failed on refresh, add error to app context + addError(monoVertexError); + } + return; + } + if (monoVertexData?.errMsg) { + if (options?.requestKey === "") { + // Failed on first load, return error + setResults({ + data: undefined, + loading: false, + error: monoVertexData?.errMsg, + refresh, + }); + } else { + // Failed on refresh, add error to app context + addError(monoVertexData?.errMsg); + } + return; + } + if (monoVertexData) { + const monoVertexSummary = { + monoVertexData: monoVertexData?.data, + }; + setResults({ + data: monoVertexSummary, + loading: false, + error: undefined, + refresh, + }); + return; + } + }, [ + monoVertexData, + monoVertexLoading, + monoVertexError, + options, + refresh, + addError, + ]); + return results; +}; diff --git a/ui/src/utils/fetchWrappers/monoVertexUpdateFetch.ts b/ui/src/utils/fetchWrappers/monoVertexUpdateFetch.ts new file mode 100644 index 0000000000..640d718dd3 --- /dev/null +++ b/ui/src/utils/fetchWrappers/monoVertexUpdateFetch.ts @@ -0,0 +1,92 @@ +import { useContext, useEffect, useState } from "react"; +import { Options, useFetch } from "./fetch"; +import { getBaseHref } from "../index"; +import { AppContextProps } from "../../types/declarations/app"; +import { AppContext } from "../../App"; +import { PipelineUpdateFetchResult } from "../../types/declarations/pipeline"; + +const DATA_REFRESH_INTERVAL = 1000; // ms + +// fetch monoVertex to check for existence +export const useMonoVertexUpdateFetch = ({ + namespaceId, + pipelineId, + active, + refreshInterval = DATA_REFRESH_INTERVAL, +}: any) => { + const [options, setOptions] = useState({ + skip: !active, + requestKey: "", + }); + + const [results, setResults] = useState({ + pipelineAvailable: false, + }); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [intervalId, setIntervalId] = useState(); + + const { host } = useContext(AppContext); + + const { data, loading, error } = useFetch( + `${host}${getBaseHref()}/api/v1/namespaces/${namespaceId}/mono-vertices/${pipelineId}`, + undefined, + options + ); + + useEffect(() => { + if (!active) { + // Clear any existing interval running + setIntervalId((prev: any) => { + if (prev) { + clearInterval(prev); + } + return undefined; + }); + return; + } + // Set periodic interval to refresh data + const id = setInterval(() => { + setOptions({ + skip: false, + requestKey: "id" + Math.random().toString(16).slice(2), + }); + }, refreshInterval); + // Clear any existing interval running and store new one + setIntervalId((prev: any) => { + if (prev) { + clearInterval(prev); + } + return id; + }); + return () => { + // Clear interval on unmount + clearInterval(id); + }; + }, [active, refreshInterval]); + + useEffect(() => { + if (loading) { + if (options?.requestKey === "") { + // Only set false when it's the first load. Keep existing result otherwise. + setResults({ + pipelineAvailable: false, + }); + } + return; + } + if (error || data?.errMsg) { + setResults({ + pipelineAvailable: false, + }); + return; + } + if (data?.data) { + setResults({ + pipelineAvailable: true, + }); + return; + } + }, [data, loading, error, options]); + + return results; +}; diff --git a/ui/src/utils/fetchWrappers/namespaceK8sEventsFetch.ts b/ui/src/utils/fetchWrappers/namespaceK8sEventsFetch.ts index e3df856db9..99b0ee434b 100644 --- a/ui/src/utils/fetchWrappers/namespaceK8sEventsFetch.ts +++ b/ui/src/utils/fetchWrappers/namespaceK8sEventsFetch.ts @@ -70,7 +70,13 @@ export const useNamespaceK8sEventsFetch = ({ if (vertex) { return `${BASE_URL}?objectType=vertex&objectName=${pipeline}-${vertex}`; } else if (pipeline) { - return `${BASE_URL}?objectType=pipeline&objectName=${pipeline}`; + const isMonoVertex = pipeline.endsWith("(MonoVertex)"); + const pipelineName = isMonoVertex + ? pipeline.replace(/\s*\(.*?\)\s*/g, "").trim() + : pipeline; + return `${BASE_URL}?objectType=${ + isMonoVertex ? "monovertex" : "pipeline" + }&objectName=${pipelineName}`; } return `${BASE_URL}`; }, [namespace, pipeline, vertex]); diff --git a/ui/src/utils/fetchWrappers/namespaceSummaryFetch.ts b/ui/src/utils/fetchWrappers/namespaceSummaryFetch.ts index 7a051f95a7..5f4a625695 100644 --- a/ui/src/utils/fetchWrappers/namespaceSummaryFetch.ts +++ b/ui/src/utils/fetchWrappers/namespaceSummaryFetch.ts @@ -12,11 +12,12 @@ import { const rawDataToNamespaceSummary = ( rawPipelineData: any[], - rawIsbData: any[] + rawIsbData: any[], + rawMonoVertexData: any[] ): NamespaceSummaryData | undefined => { - const pipelinesCount = Array.isArray(rawPipelineData) - ? rawPipelineData.length - : 0; + const pipelinesCount = + (Array.isArray(rawPipelineData) ? rawPipelineData.length : 0) + + (Array.isArray(rawMonoVertexData) ? rawMonoVertexData.length : 0); let pipelinesActiveCount = 0; let pipelinesInactiveCount = 0; let pipelinesHealthyCount = 0; @@ -56,6 +57,34 @@ const rawDataToNamespaceSummary = ( status: pipeline.status, }); }); + // adding MonoVertex count to pipeline count + Array.isArray(rawMonoVertexData) && + rawMonoVertexData?.forEach((monoVertex: any) => { + switch (monoVertex.status) { + case "healthy": + pipelinesActiveCount++; + pipelinesHealthyCount++; + break; + case "warning": + pipelinesActiveCount++; + pipelinesWarningCount++; + break; + case "critical": + pipelinesActiveCount++; + pipelinesCriticalCount++; + break; + case "inactive": + pipelinesInactiveCount++; + break; + default: + break; + } + // Add pipeline summary to array + pipelineSummaries.push({ + name: monoVertex.name, + status: monoVertex.status, + }); + }); Array.isArray(rawIsbData) && rawIsbData?.forEach((isb: any) => { switch (isb.status) { @@ -142,6 +171,15 @@ export const useNamespaceSummaryFetch = ({ undefined, options ); + const { + data: monoVertexData, + loading: monoVertexLoading, + error: monoVertexError, + } = useFetch( + `${host}${getBaseHref()}/api/v1/namespaces/${namespace}/mono-vertices`, + undefined, + options + ); useEffect(() => { setInterval(() => { @@ -153,7 +191,7 @@ export const useNamespaceSummaryFetch = ({ }, []); useEffect(() => { - if (pipelineLoading || isbLoading) { + if (pipelineLoading || isbLoading || monoVertexLoading) { if (options?.requestKey === "" || loadOnRefresh) { // Only set loading true when first load or when loadOnRefresh is true setResults({ @@ -165,38 +203,41 @@ export const useNamespaceSummaryFetch = ({ } return; } - if (pipelineError || isbError) { + if (pipelineError || isbError || monoVertexError) { if (options?.requestKey === "") { // Failed on first load, return error setResults({ data: undefined, loading: false, - error: pipelineError || isbError, + error: pipelineError || isbError || monoVertexError, refresh, }); } else { // Failed on refresh, add error to app context - addError(pipelineError || isbError); + addError(pipelineError || isbError || monoVertexError); } return; } - if (pipelineData?.errMsg || isbData?.errMsg) { + if (pipelineData?.errMsg || isbData?.errMsg || monoVertexData?.errMsg) { if (options?.requestKey === "") { // Failed on first load, return error setResults({ data: undefined, loading: false, - error: pipelineData?.errMsg || isbData?.errMsg, + error: + pipelineData?.errMsg || isbData?.errMsg || monoVertexData?.errMsg, refresh, }); } else { // Failed on refresh, add error to app context - addError(pipelineData?.errMsg || isbData?.errMsg); + addError( + pipelineData?.errMsg || isbData?.errMsg || monoVertexData?.errMsg + ); } return; } - if (pipelineData && isbData) { - const pipeLineMap = pipelineData?.data?.reduce((map: any, obj: any) => { + if (pipelineData && isbData && monoVertexData) { + const pipelineMap = pipelineData?.data?.reduce((map: any, obj: any) => { map[obj.name] = obj; return map; }, {}); @@ -204,14 +245,23 @@ export const useNamespaceSummaryFetch = ({ map[obj.name] = obj; return map; }, {}); + const monoVertexMap = monoVertexData?.data?.reduce( + (map: any, obj: any) => { + map[obj.name] = obj; + return map; + }, + {} + ); const nsSummary = rawDataToNamespaceSummary( pipelineData?.data, - isbData?.data + isbData?.data, + monoVertexData?.data ); setResults({ data: nsSummary, - pipelineRawData: pipeLineMap, + pipelineRawData: pipelineMap, isbRawData: isbMap, + monoVertexRawData: monoVertexMap, loading: false, error: undefined, refresh, @@ -221,10 +271,13 @@ export const useNamespaceSummaryFetch = ({ }, [ pipelineData, isbData, + monoVertexData, pipelineLoading, isbLoading, + monoVertexLoading, pipelineError, isbError, + monoVertexError, loadOnRefresh, options, refresh, diff --git a/ui/src/utils/fetcherHooks/monoVertexViewFetch.ts b/ui/src/utils/fetcherHooks/monoVertexViewFetch.ts new file mode 100644 index 0000000000..9c7cd459d3 --- /dev/null +++ b/ui/src/utils/fetcherHooks/monoVertexViewFetch.ts @@ -0,0 +1,278 @@ +import { useCallback, useContext, useEffect, useMemo, useState } from "react"; +import { Node } from "reactflow"; +import { isEqual } from "lodash"; +import { getBaseHref } from "../index"; +import { AppContextProps } from "../../types/declarations/app"; +import { AppContext } from "../../App"; +import { + MonoVertex, + MonoVertexSpec, + MonoVertexMetrics, +} from "../../types/declarations/pipeline"; + +export const useMonoVertexViewFetch = ( + namespaceId: string | undefined, + pipelineId: string | undefined, + addError: (error: string) => void +) => { + const [requestKey, setRequestKey] = useState(""); + const [pipeline, setPipeline] = useState(undefined); + const [spec, setSpec] = useState(undefined); + const [monoVertexPods, setMonoVertexPods] = useState>( + new Map() + ); + const [monoVertexMetrics, setMonoVertexMetrics] = useState< + Map + >(new Map()); + const [pipelineErr, setPipelineErr] = useState(undefined); + const [loading, setLoading] = useState(true); + const { host } = useContext(AppContext); + + const BASE_API = `${host}${getBaseHref()}/api/v1/namespaces/${namespaceId}/mono-vertices/${pipelineId}`; + + const refresh = useCallback(() => { + setRequestKey(`${Date.now()}`); + }, []); + + // Call to get pipeline + useEffect(() => { + const fetchPipeline = async () => { + try { + const response = await fetch(`${BASE_API}?refreshKey=${requestKey}`); + if (response.ok) { + const json = await response.json(); + if (json?.data) { + // Update pipeline state with data from the response + setPipeline(json.data?.monoVertex); + // Update spec state if it is not equal to the spec from the response + if (!isEqual(spec, json.data)) setSpec(json.data?.monoVertex?.spec); + setPipelineErr(undefined); + } else if (json?.errMsg) { + // pipeline API call returns an error message + if (requestKey === "") { + setPipelineErr(json.errMsg); + } else { + addError(json.errMsg); + } + } + } else { + // Handle the case when the response is not OK + if (requestKey === "") { + if (response.status === 403) { + // Unauthorized user, display given or default error message + const data = await response.json(); + if (data.errMsg) { + setPipelineErr(`Error: ${data.errMsg}`); + } else { + setPipelineErr( + `Error: user is not authorized to execute the requested action.` + ); + } + } else { + setPipelineErr(`Response code: ${response.status}`); + } + } else { + addError(`Failed with code: ${response.status}`); + } + } + } catch (e: any) { + // Handle any errors that occur during the fetch request + if (requestKey === "") { + setPipelineErr(e.message); + } else { + addError(e.message); + } + } + }; + + fetchPipeline(); + }, [requestKey, addError]); + + // Refresh pipeline every 30 sec + useEffect(() => { + const interval = setInterval(() => { + setRequestKey(`${Date.now()}`); + }, 30000); + return () => clearInterval(interval); + }, []); + + // This useEffect is used to obtain all the pods for a given monoVertex. + useEffect(() => { + const vertexToPodsMap = new Map(); + if (spec?.source && spec?.sink) { + // Fetch pods count for each vertex in parallel + Promise.allSettled([ + fetch(`${BASE_API}/pods`) + .then((response) => { + if (response.ok) { + return response.json(); + } else { + return Promise.reject({ response, vertex: pipelineId }); + } + }) + .then((json) => { + if (json?.data) { + const mvtxPods = json.data.filter( + (mvtx: any) => !mvtx?.metadata?.name.includes("-daemon-") + ); + // Update vertexToPodsMap with the number of pods for the current vertex + vertexToPodsMap.set(pipelineId, mvtxPods?.length); + } else if (json?.errMsg) { + // Pods API call returns an error message + addError(json.errMsg); + } + }), + ]) + .then((results) => { + results.forEach((result) => { + if (result && result?.status === "rejected") { + // Handle rejected promises and add error messages to podsErr + addError(`Failed to get pods: ${result.reason.response.status}`); + } + }); + }) + .then(() => { + if (!isEqual(monoVertexPods, vertexToPodsMap)) { + // Update vertexPods state if it is not equal to vertexToPodsMap + setMonoVertexPods(vertexToPodsMap); + } + }) + .catch((e: any) => { + addError(`Error: ${e.message}`); + }); + } + }, [spec, requestKey, addError]); + + const getVertexMetrics = useCallback(() => { + const vertexToMetricsMap = new Map(); + + if (spec?.source && spec?.sink && monoVertexPods.size > 0) { + // Fetch metrics for monoVertex + Promise.allSettled([ + fetch(`${BASE_API}/metrics`) + .then((response) => { + if (response.ok) { + return response.json(); + } else { + return Promise.reject(response); + } + }) + .then((json) => { + if (json?.data) { + const mvtx = json.data; + const monoVertexName = mvtx.monoVertex; + const monoVertexMetrics: MonoVertexMetrics = { + ratePerMin: "0.00", + ratePerFiveMin: "0.00", + ratePerFifteenMin: "0.00", + podMetrics: [], + error: false, + }; + let ratePerMin = 0.0, + ratePerFiveMin = 0.0, + ratePerFifteenMin = 0.0; + // Calculate processing rates as summation of pod values + if ("processingRates" in mvtx) { + if ("1m" in mvtx["processingRates"]) { + ratePerMin += mvtx["processingRates"]["1m"]; + } + if ("5m" in mvtx["processingRates"]) { + ratePerFiveMin += mvtx["processingRates"]["5m"]; + } + if ("15m" in mvtx["processingRates"]) { + ratePerFifteenMin += mvtx["processingRates"]["15m"]; + } + } else { + if ( + monoVertexPods.has(monoVertexName) && + monoVertexPods.get(monoVertexName) !== 0 + ) { + // Handle case when processingRates are not available for a vertex + monoVertexMetrics.error = true; + addError( + `Failed to get metrics for ${monoVertexName} monoVertex` + ); + } + } + monoVertexMetrics.ratePerMin = ratePerMin.toFixed(2); + monoVertexMetrics.ratePerFiveMin = ratePerFiveMin.toFixed(2); + monoVertexMetrics.ratePerFifteenMin = + ratePerFifteenMin.toFixed(2); + if ( + monoVertexPods.has(monoVertexName) && + monoVertexPods.get(monoVertexName) !== 0 + ) { + monoVertexMetrics.podMetrics = json; + } + vertexToMetricsMap.set(monoVertexName, monoVertexMetrics); + } else if (json?.errMsg) { + // Metrics API call returns an error message + addError(json.errMsg); + } + }), + ]) + .then((results) => { + results.forEach((result) => { + if (result && result?.status === "rejected") { + // Handle rejected promises and add error messages to metricsErr + addError( + `Failed to get metrics: ${result.reason.response.status}` + ); + } + }); + }) + .then(() => setMonoVertexMetrics(vertexToMetricsMap)) + .catch((e: any) => { + addError(`Error: ${e.message}`); + }); + } + }, [spec, monoVertexPods, addError]); + + // This useEffect is used to obtain metrics for a given monoVertex and refreshes every 1 minute + useEffect(() => { + getVertexMetrics(); + const interval = setInterval(() => { + getVertexMetrics(); + }, 60000); + return () => clearInterval(interval); + }, [getVertexMetrics]); + + const vertices = useMemo(() => { + const newVertices: Node[] = []; + // if (spec?.vertices && vertexPods && vertexMetrics) { + if (spec?.source && spec?.sink && monoVertexMetrics) { + const newNode = {} as Node; + const name = pipelineId ?? ""; + newNode.id = name; + newNode.data = { name: name }; + newNode.data.podnum = spec?.replicas ? spec.replicas : 0; + newNode.position = { x: 0, y: 0 }; + // change this in the future if you would like to make it draggable + newNode.draggable = false; + newNode.type = "custom"; + newNode.data.nodeInfo = spec; + newNode.data.type = "monoVertex"; + newNode.data.vertexMetrics = null; + newNode.data.vertexMetrics = monoVertexMetrics.has(name) + ? monoVertexMetrics.get(name) + : null; + newVertices.push(newNode); + } + return newVertices; + }, [spec, monoVertexMetrics]); + + //sets loading variable + useEffect(() => { + if (pipeline && vertices?.length > 0) { + setLoading(false); + } + }, [pipeline, vertices]); + + return { + pipeline, + vertices, + pipelineErr, + loading, + refresh, + }; +}; diff --git a/ui/src/utils/fetcherHooks/podsViewFetch.test.ts b/ui/src/utils/fetcherHooks/podsViewFetch.test.ts index 0948c1fbc8..b113a7bf93 100644 --- a/ui/src/utils/fetcherHooks/podsViewFetch.test.ts +++ b/ui/src/utils/fetcherHooks/podsViewFetch.test.ts @@ -65,6 +65,7 @@ describe("Custom Pods hook", () => { "simple-pipeline", "cat", undefined, + "udf", jest.fn() as Dispatch>, jest.fn() as Dispatch> ) @@ -88,6 +89,7 @@ describe("Custom Pods hook", () => { "simple-pipeline", "cat", undefined, + "udf", jest.fn() as Dispatch>, jest.fn() as Dispatch> ) @@ -109,6 +111,7 @@ describe("Custom Pods hook", () => { "simple-pipeline", "cat", undefined, + "udf", jest.fn() as Dispatch>, jest.fn() as Dispatch> ) diff --git a/ui/src/utils/fetcherHooks/podsViewFetch.ts b/ui/src/utils/fetcherHooks/podsViewFetch.ts index 9c57592428..e893381cf6 100644 --- a/ui/src/utils/fetcherHooks/podsViewFetch.ts +++ b/ui/src/utils/fetcherHooks/podsViewFetch.ts @@ -19,6 +19,7 @@ export const usePodsViewFetch = ( pipelineId: string | undefined, vertexId: string | undefined, selectedPod: Pod | undefined, + type: string, setSelectedPod: Dispatch>, setSelectedContainer: Dispatch> ) => { @@ -39,12 +40,19 @@ export const usePodsViewFetch = ( const fetchPods = async () => { try { const response = await fetch( - `${host}${getBaseHref()}/api/v1/namespaces/${namespaceId}/pipelines/${pipelineId}/vertices/${vertexId}/pods?refreshKey=${requestKey}` + `${host}${getBaseHref()}/api/v1/namespaces/${namespaceId}${ + type === "monoVertex" + ? `/mono-vertices` + : `/pipelines/${pipelineId}/vertices` + }/${vertexId}/pods?refreshKey=${requestKey}` ); if (response.ok) { const json = await response.json(); if (json?.data) { - const data = json?.data; + let data = json?.data; + data = data.filter( + (pod: any) => !pod?.metadata?.name.includes("-daemon-") + ); const pList = data?.map((pod: any) => { const containers: string[] = []; const containerSpecMap = new Map();