Repository: numaproj/numaflow
Pull request #1289 (2023-10-27 23:33:32)

add tooltips
- add tooltips
- summaries make it all left aligned
- style the fonts for tooltip

<img width="380" alt="Screenshot 2023-10-31 at 11 47 22 AM" src="https://github.com/numaproj/numaflow/assets/28713078/2112b493-a66c-464b-afc1-cda9be3b228e">
<img width="368" alt="Screenshot 2023-10-31 at 11 47 14 AM" src="https://github.com/numaproj/numaflow/assets/28713078/380fb8fd-368a-4cd4-ab7c-d16d48cc8079">
<img width="424" alt="Screenshot 2023-10-31 at 11 47 08 AM" src="https://github.com/numaproj/numaflow/assets/28713078/1bb0f3ec-2fa2-48c5-ba4c-967d16be27dc">
<img width="387" alt="Screenshot 2023-10-31 at 11 47 03 AM" src="https://github.com/numaproj/numaflow/assets/28713078/bd7cbbcb-6ef1-4ab6-894d-5f018c896ea1">
<img width="1780" alt="Screenshot 2023-10-31 at 11 48 47 AM" src="https://github.com/numaproj/numaflow/assets/28713078/6a9b0e4d-b542-4ab5-8631-0f9a9c30f91a">
<img width="1783" alt="Screenshot 2023-10-31 at 11 48 38 AM" src="https://github.com/numaproj/numaflow/assets/28713078/0068fc3d-5c61-4b03-8e96-c1379b9bdf26">
<img width="1789" alt="Screenshot 2023-10-31 at 11 48 29 AM" src="https://github.com/numaproj/numaflow/assets/28713078/cf4777ea-cf33-47b8-aed4-e22eda3c8bb6">



Patch:
diff --git a/pkg/daemon/server/service/pipeline_metrics_query.go b/pkg/daemon/server/service/pipeline_metrics_query.go
index 22c63d44..5df96c1d 100644
--- a/pkg/daemon/server/service/pipeline_metrics_query.go
+++ b/pkg/daemon/server/service/pipeline_metrics_query.go
@@ -31,7 +31,7 @@ import (
 
 	"github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1"
 	"github.com/numaproj/numaflow/pkg/apis/proto/daemon"
-	server "github.com/numaproj/numaflow/pkg/daemon/server/service/rater"
+	rater "github.com/numaproj/numaflow/pkg/daemon/server/service/rater"
 	"github.com/numaproj/numaflow/pkg/isbsvc"
 	"github.com/numaproj/numaflow/pkg/metrics"
 	"github.com/numaproj/numaflow/pkg/shared/logging"
@@ -50,7 +50,7 @@ type pipelineMetadataQuery struct {
 	pipeline          *v1alpha1.Pipeline
 	httpClient        metricsHttpClient
 	watermarkFetchers map[v1alpha1.Edge][]fetch.UXFetcher
-	rater             server.Ratable
+	rater             rater.Ratable
 }
 
 const (
@@ -64,7 +64,7 @@ func NewPipelineMetadataQuery(
 	isbSvcClient isbsvc.ISBService,
 	pipeline *v1alpha1.Pipeline,
 	wmFetchers map[v1alpha1.Edge][]fetch.UXFetcher,
-	rater server.Ratable) (*pipelineMetadataQuery, error) {
+	rater rater.Ratable) (*pipelineMetadataQuery, error) {
 	var err error
 	ps := pipelineMetadataQuery{
 		isbSvcClient: isbSvcClient,
diff --git a/pkg/daemon/server/service/rater/doc.go b/pkg/daemon/server/service/rater/doc.go
new file mode 100644
index 00000000..aa1060e0
--- /dev/null
+++ b/pkg/daemon/server/service/rater/doc.go
@@ -0,0 +1,19 @@
+/*
+Copyright 2022 The Numaproj Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package rater provides the functionality to calculate the processing rate of each vertex partition.
+// The processing rate is calculated based on the metric forwarder_data_read_total.
+package rater
diff --git a/pkg/daemon/server/service/rater/helper.go b/pkg/daemon/server/service/rater/helper.go
index f680eb94..929ac99d 100644
--- a/pkg/daemon/server/service/rater/helper.go
+++ b/pkg/daemon/server/service/rater/helper.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package server
+package rater
 
 import (
 	"time"
diff --git a/pkg/daemon/server/service/rater/options.go b/pkg/daemon/server/service/rater/options.go
index 4a798b51..03fdc53d 100644
--- a/pkg/daemon/server/service/rater/options.go
+++ b/pkg/daemon/server/service/rater/options.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package server
+package rater
 
 type options struct {
 	// Number of workers working on collecting counts of processed messages.
diff --git a/pkg/daemon/server/service/rater/pod_tracker.go b/pkg/daemon/server/service/rater/pod_tracker.go
index 4fb2fc49..c6936309 100644
--- a/pkg/daemon/server/service/rater/pod_tracker.go
+++ b/pkg/daemon/server/service/rater/pod_tracker.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package server
+package rater
 
 import (
 	"context"
@@ -30,10 +30,10 @@ import (
 	"github.com/numaproj/numaflow/pkg/shared/logging"
 )
 
-// PodInfoSeparator is used as a separator such that the worker can split the pod key
-// to get the pipeline name, vertex name, pod index and vertex type.
+// podInfoSeparator is used as a separator to split the pod key
+// to get the pipeline name, vertex name, and pod index.
 // "*" is chosen because it is not allowed in all the above fields.
-const PodInfoSeparator = "*"
+const podInfoSeparator = "*"
 
 // PodTracker maintains a set of active pods for a pipeline
 // It periodically sends http requests to pods to check if they are still active
@@ -131,9 +131,28 @@ func (pt *PodTracker) GetActivePodsCount() int {
 	return pt.activePods.Length()
 }
 
+// podInfo represents the information of a pod that is used for tracking the processing rate
+type podInfo struct {
+	pipelineName string
+	vertexName   string
+	podName      string
+}
+
+func (pt *PodTracker) GetPodInfo(key string) (*podInfo, error) {
+	pi := strings.Split(key, podInfoSeparator)
+	if len(pi) != 3 {
+		return nil, fmt.Errorf("invalid key %q", key)
+	}
+	return &podInfo{
+		pipelineName: pi[0],
+		vertexName:   pi[1],
+		podName:      strings.Join([]string{pi[0], pi[1], pi[2]}, "-"),
+	}, nil
+}
+
 func (pt *PodTracker) getPodKey(index int, vertexName string) string {
 	// podKey is used as a unique identifier for the pod, it is used by worker to determine the count of processed messages of the pod.
-	return strings.Join([]string{pt.pipeline.Name, vertexName, fmt.Sprintf("%d", index)}, PodInfoSeparator)
+	return strings.Join([]string{pt.pipeline.Name, vertexName, fmt.Sprintf("%d", index)}, podInfoSeparator)
 }
 
 func (pt *PodTracker) isActive(vertexName, podName string) bool {
diff --git a/pkg/daemon/server/service/rater/rater.go b/pkg/daemon/server/service/rater/rater.go
index d3060cd1..c7d232ea 100644
--- a/pkg/daemon/server/service/rater/rater.go
+++ b/pkg/daemon/server/service/rater/rater.go
@@ -14,14 +14,13 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package server
+package rater
 
 import (
 	"context"
 	"crypto/tls"
 	"fmt"
 	"net/http"
-	"strings"
 	"time"
 
 	"github.com/prometheus/common/expfmt"
@@ -38,6 +37,8 @@ type Ratable interface {
 	GetRates(vertexName, partitionName string) map[string]float64
 }
 
+var _ Ratable = (*Rater)(nil)
+
 // CountWindow is the time window for which we maintain the timestamped counts, currently 10 seconds
 // e.g., if the current time is 12:00:07,
 // the retrieved count will be tracked in the 12:00:00-12:00:10 time window using 12:00:10 as the timestamp
@@ -133,24 +134,22 @@ func (r *Rater) monitor(ctx context.Context, id int, keyCh <-chan string) {
 func (r *Rater) monitorOnePod(ctx context.Context, key string, worker int) error {
 	log := logging.FromContext(ctx).With("worker", fmt.Sprint(worker)).With("podKey", key)
 	log.Debugf("Working on key: %s", key)
-	podInfo := strings.Split(key, PodInfoSeparator)
-	if len(podInfo) != 3 {
-		return fmt.Errorf("invalid key %q", key)
+	podInfo, err := r.podTracker.GetPodInfo(key)
+	if err != nil {
+		return err
 	}
-	vertexName := podInfo[1]
-	podName := strings.Join([]string{podInfo[0], podInfo[1], podInfo[2]}, "-")
 	var podReadCount *PodReadCount
 	if r.podTracker.IsActive(key) {
-		podReadCount = r.getPodReadCounts(vertexName, podName)
+		podReadCount = r.getPodReadCounts(podInfo.vertexName, podInfo.podName)
 		if podReadCount == nil {
-			log.Debugf("Failed retrieving total podReadCount for pod %s", podName)
+			log.Debugf("Failed retrieving total podReadCount for pod %s", podInfo.podName)
 		}
 	} else {
-		log.Debugf("Pod %s does not exist, updating it with nil...", podName)
+		log.Debugf("Pod %s does not exist, updating it with nil...", podInfo.podName)
 		podReadCount = nil
 	}
 	now := time.Now().Add(CountWindow).Truncate(CountWindow).Unix()
-	UpdateCount(r.timestampedPodCounts[vertexName], now, podReadCount)
+	UpdateCount(r.timestampedPodCounts[podInfo.vertexName], now, podReadCount)
 	return nil
 }
 
diff --git a/pkg/daemon/server/service/rater/timestamped_counts.go b/pkg/daemon/server/service/rater/timestamped_counts.go
index 207771b4..436deeee 100644
--- a/pkg/daemon/server/service/rater/timestamped_counts.go
+++ b/pkg/daemon/server/service/rater/timestamped_counts.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package server
+package rater
 
 import (
 	"fmt"
diff --git a/pkg/daemon/server/service/rater/uniq_str_list.go b/pkg/daemon/server/service/rater/uniq_str_list.go
index 2131c510..8bc13f10 100644
--- a/pkg/daemon/server/service/rater/uniq_str_list.go
+++ b/pkg/daemon/server/service/rater/uniq_str_list.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package server
+package rater
 
 import (
 	"container/list"
diff --git a/pkg/forward/doc.go b/pkg/forward/doc.go
index 8e8c01c3..a9141c6d 100644
--- a/pkg/forward/doc.go
+++ b/pkg/forward/doc.go
@@ -11,5 +11,5 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-// Package forward is used for creating a data forwarder for the map UDF and sink vertex.
+// Package forward is used for creating a data forwarder for the map UDF vertex.
 package forward
diff --git a/pkg/reduce/data_forward.go b/pkg/reduce/data_forward.go
index e208781e..7ce492e2 100644
--- a/pkg/reduce/data_forward.go
+++ b/pkg/reduce/data_forward.go
@@ -287,7 +287,7 @@ func (df *DataForward) associatePBQAndPnF(ctx context.Context, partitionID parti
 		}
 		pbqErr = wait.ExponentialBackoffWithContext(ctx, infiniteBackoff, func() (done bool, err error) {
 			var attempt int
-			q, pbqErr = df.pbqManager.CreateNewPBQ(ctx, partitionID, kw)
+			q, pbqErr = df.pbqManager.CreateNewPBQ(ctx, partitionID)
 			if pbqErr != nil {
 				attempt += 1
 				df.log.Warnw("Failed to create pbq during startup, retrying", zap.Any("attempt", attempt), zap.String("partitionID", partitionID.String()), zap.Error(pbqErr))
diff --git a/pkg/reduce/pbq/pbq.go b/pkg/reduce/pbq/pbq.go
index d8c83af7..b2e3963a 100644
--- a/pkg/reduce/pbq/pbq.go
+++ b/pkg/reduce/pbq/pbq.go
@@ -22,14 +22,12 @@ import (
 	"strconv"
 	"sync"
 
-	"github.com/numaproj/numaflow/pkg/metrics"
-	"github.com/numaproj/numaflow/pkg/reduce/pbq/partition"
-	"github.com/numaproj/numaflow/pkg/reduce/pbq/store"
-	"github.com/numaproj/numaflow/pkg/window"
-
 	"go.uber.org/zap"
 
 	"github.com/numaproj/numaflow/pkg/isb"
+	"github.com/numaproj/numaflow/pkg/metrics"
+	"github.com/numaproj/numaflow/pkg/reduce/pbq/partition"
+	"github.com/numaproj/numaflow/pkg/reduce/pbq/store"
 )
 
 var ErrCOB = errors.New("error while writing to pbq, pbq is closed")
@@ -47,7 +45,6 @@ type PBQ struct {
 	options       *options
 	manager       *Manager
 	log           *zap.SugaredLogger
-	kw            window.AlignedKeyedWindower
 	mu            sync.Mutex
 }
 
diff --git a/pkg/reduce/pbq/pbqmanager.go b/pkg/reduce/pbq/pbqmanager.go
index 739b8cb1..31be1fc3 100644
--- a/pkg/reduce/pbq/pbqmanager.go
+++ b/pkg/reduce/pbq/pbqmanager.go
@@ -37,12 +37,24 @@ import (
 	"github.com/numaproj/numaflow/pkg/shared/logging"
 )
 
-// RegisteredWindow to track the number of windows for a start and end time.
-type RegisteredWindow struct {
-	window.AlignedKeyedWindower
+// registeredWindow to track the number of windows for a start and end time.
+type registeredWindow struct {
+	startTime time.Time
+	endTime   time.Time
+	// count how many slots do we have for this window. currently we only use slot-0, so count is always 1
 	count *atomic.Int32
 }
 
+var _ window.AlignedWindower = (*registeredWindow)(nil)
+
+func (r *registeredWindow) StartTime() time.Time {
+	return r.startTime
+}
+
+func (r *registeredWindow) EndTime() time.Time {
+	return r.endTime
+}
+
 // Manager helps in managing the lifecycle of PBQ instances
 type Manager struct {
 	vertexName    string
@@ -52,7 +64,7 @@ type Manager struct {
 	pbqOptions    *options
 	pbqMap        map[string]*PBQ
 	log           *zap.SugaredLogger
-	yetToBeClosed *window.SortedWindowList[*RegisteredWindow]
+	yetToBeClosed *window.SortedWindowList[*registeredWindow]
 	// we need lock to access pbqMap, since deregister will be called inside pbq
 	// and each pbq will be inside a go routine, and also entire PBQ could be managed
 	// through a go routine (depends on the orchestrator)
@@ -79,14 +91,14 @@ func NewManager(ctx context.Context, vertexName string, pipelineName string, vr
 		pbqMap:        make(map[string]*PBQ),
 		pbqOptions:    pbqOpts,
 		log:           logging.FromContext(ctx),
-		yetToBeClosed: window.NewSortedWindowList[*RegisteredWindow](),
+		yetToBeClosed: window.NewSortedWindowList[*registeredWindow](),
 	}
 
 	return pbqManager, nil
 }
 
 // CreateNewPBQ creates new pbq for a partition
-func (m *Manager) CreateNewPBQ(ctx context.Context, partitionID partition.ID, win window.AlignedKeyedWindower) (ReadWriteCloser, error) {
+func (m *Manager) CreateNewPBQ(ctx context.Context, partitionID partition.ID) (ReadWriteCloser, error) {
 	persistentStore, err := m.storeProvider.CreateStore(ctx, partitionID)
 	if err != nil {
 		return nil, fmt.Errorf("failed to create a PBQ store, %w", err)
@@ -103,7 +115,6 @@ func (m *Manager) CreateNewPBQ(ctx context.Context, partitionID partition.ID, wi
 		PartitionID:   partitionID,
 		options:       m.pbqOptions,
 		manager:       m,
-		kw:            win,
 		log:           logging.FromContext(ctx).With("PBQ", partitionID),
 	}
 	m.register(partitionID, p)
@@ -208,9 +219,10 @@ func (m *Manager) ShutDown(ctx context.Context) {
 
 // register is intended to be used by PBQ to register itself with the manager.
 func (m *Manager) register(partitionID partition.ID, p *PBQ) {
-	ww := &RegisteredWindow{
-		AlignedKeyedWindower: p.kw,
-		count:                atomic.NewInt32(0),
+	ww := &registeredWindow{
+		startTime: partitionID.Start,
+		endTime:   partitionID.End,
+		count:     atomic.NewInt32(0),
 	}
 
 	ww, _ = m.yetToBeClosed.InsertIfNotPresent(ww)
@@ -235,10 +247,12 @@ func (m *Manager) register(partitionID partition.ID, p *PBQ) {
 func (m *Manager) deregister(partitionID partition.ID) error {
 
 	m.Lock()
-	ww := &RegisteredWindow{
-		AlignedKeyedWindower: m.pbqMap[partitionID.String()].kw,
-		count:                atomic.NewInt32(0),
+	ww := &registeredWindow{
+		startTime: partitionID.Start,
+		endTime:   partitionID.End,
+		count:     atomic.NewInt32(0),
 	}
+
 	delete(m.pbqMap, partitionID.String())
 	m.Unlock()
 
@@ -295,7 +309,7 @@ func (m *Manager) Replay(ctx context.Context) {
 // conservative. PBQManager's view of next window to be materialized is conservative as it is on the reading side.
 // We SHOULD NOT use NextWindowToBeMaterialized to write data to because it could fail (channel could have been closed but
 // GC is yet to happen), this function should only be on readonly path.
-func (m *Manager) NextWindowToBeMaterialized() window.AlignedKeyedWindower {
+func (m *Manager) NextWindowToBeMaterialized() window.AlignedWindower {
 	if m.yetToBeClosed.Len() == 0 {
 		return nil
 	}
diff --git a/ui/src/components/common/ErrorIndicator/index.tsx b/ui/src/components/common/ErrorIndicator/index.tsx
index 9488b58c..fd7400e8 100644
--- a/ui/src/components/common/ErrorIndicator/index.tsx
+++ b/ui/src/components/common/ErrorIndicator/index.tsx
@@ -1,11 +1,13 @@
 import React, { useContext, useCallback } from "react";
 import Box from "@mui/material/Box";
 import Paper from "@mui/material/Paper";
+import Tooltip from "@mui/material/Tooltip";
 import { AppContextProps } from "../../../types/declarations/app";
 import { AppContext } from "../../../App";
 import { SidebarType } from "../SlidingSidebar";
 import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
 import ErrorIcon from "@mui/icons-material/Error";
+import { ERRORS_TOOLTIP } from "../../../utils";
 
 import "./style.css";
 
@@ -20,24 +22,28 @@ export function ErrorIndicator() {
   }, []);
 
   return (
-    <Paper
-      elevation={1}
-      sx={{
-        cursor: "pointer",
-        padding: "0.25rem 0.5rem",
-      }}
-      onClick={onErrorClick}
-    >
-      <Box sx={{ display: "flex", flexDirection: "row", alignItems: "center" }}>
-        {errors && errors.length ? (
-          <ErrorIcon sx={{ color: "#D52B1E" }} />
-        ) : (
-          <ErrorOutlineIcon sx={{ color: "#6B6C72" }} />
-        )}
-        {errors.length ? (
-          <span className="error-indicator-text">Error occurred</span>
-        ) : undefined}
-      </Box>
-    </Paper>
+    <Tooltip title={ERRORS_TOOLTIP} arrow>
+      <Paper
+        elevation={1}
+        sx={{
+          cursor: "pointer",
+          padding: "0.25rem 0.5rem",
+        }}
+        onClick={onErrorClick}
+      >
+        <Box
+          sx={{ display: "flex", flexDirection: "row", alignItems: "center" }}
+        >
+          {errors && errors.length ? (
+            <ErrorIcon sx={{ color: "#D52B1E" }} />
+          ) : (
+            <ErrorOutlineIcon sx={{ color: "#6B6C72" }} />
+          )}
+          {errors.length ? (
+            <span className="error-indicator-text">Error occurred</span>
+          ) : undefined}
+        </Box>
+      </Paper>
+    </Tooltip>
   );
 }
diff --git a/ui/src/components/common/Help/index.tsx b/ui/src/components/common/Help/index.tsx
new file mode 100644
index 00000000..dbe65286
--- /dev/null
+++ b/ui/src/components/common/Help/index.tsx
@@ -0,0 +1,15 @@
+import HelpIcon from "@mui/icons-material/Help";
+import { HTMLlTooltip } from "../../../utils";
+export const Help = ({ tooltip }) => {
+  return (
+    <HTMLlTooltip title={tooltip} arrow>
+      <HelpIcon
+        sx={{
+          fontSize: 14,
+          alignItems: "right",
+          color: "#b2b2b2",
+        }}
+      />
+    </HTMLlTooltip>
+  );
+};
diff --git a/ui/src/components/common/SlidingSidebar/partials/K8sEvents/index.tsx b/ui/src/components/common/SlidingSidebar/partials/K8sEvents/index.tsx
index 2f6955fe..f5ec6306 100644
--- a/ui/src/components/common/SlidingSidebar/partials/K8sEvents/index.tsx
+++ b/ui/src/components/common/SlidingSidebar/partials/K8sEvents/index.tsx
@@ -11,30 +11,51 @@ import Pagination from "@mui/material/Pagination";
 import ToggleButtonGroup from "@mui/material/ToggleButtonGroup";
 import ToggleButton from "@mui/material/ToggleButton";
 import CircularProgress from "@mui/material/CircularProgress";
+import Select, { SelectChangeEvent } from "@mui/material/Select";
+import MenuItem from "@mui/material/MenuItem";
 import { K8sEvent } from "../../../../../types/declarations/namespace";
+import { ErrorDisplay } from "../../../ErrorDisplay";
 import { useNamespaceK8sEventsFetch } from "../../../../../utils/fetchWrappers/namespaceK8sEventsFetch";
 
 import "./style.css";
 
 const MAX_PAGE_SIZE = 6;
+const DEFAULT_FILTER_VALUE = "All";
 
 export interface K8sEventsProps {
   namespaceId: string;
+  pipelineId?: string;
+  vertexId?: string;
+  headerText?: string;
   excludeHeader?: boolean;
   square?: boolean;
+  pipelineFilterOptions?: string[];
+  vertexFilterOptions?: Map<string, string[]>;
 }
 
 export function K8sEvents({
   namespaceId,
+  pipelineId,
+  vertexId,
+  headerText = "Namespace K8s Events",
   excludeHeader = false,
   square = false,
+  pipelineFilterOptions = [],
+  vertexFilterOptions = new Map<string, string[]>(),
 }: K8sEventsProps) {
+  const [selectedPipeline, setSelectedPipeline] =
+    useState<string>(DEFAULT_FILTER_VALUE);
+  const [selectedVertex, setSelectedVertex] =
+    useState<string>(DEFAULT_FILTER_VALUE);
   const [page, setPage] = useState(1);
   const [totalPages, setTotalPages] = useState(0);
   const [typeFilter, setTypeFilter] = useState<string | undefined>();
-  const [filteredEvents, setFilteredEvents] = React.useState<K8sEvent[]>([]);
+  const [filteredEvents, setFilteredEvents] = useState<K8sEvent[]>([]);
   const { data, loading, error } = useNamespaceK8sEventsFetch({
     namespace: namespaceId,
+    pipeline:
+      selectedPipeline === DEFAULT_FILTER_VALUE ? pipelineId : selectedPipeline,
+    vertex: selectedVertex === DEFAULT_FILTER_VALUE ? vertexId : selectedVertex,
   });
 
   // Update filtered events based on page selected and filter
@@ -85,11 +106,22 @@ export function K8sEvents({
     []
   );
 
-  const typeCounts = useMemo(() => {
-    if (!data) {
-      return undefined;
-    }
+  const handlePipelineFilterChange = useCallback(
+    (e: SelectChangeEvent<string>) => {
+      setSelectedPipeline(e.target.value);
+      setSelectedVertex(DEFAULT_FILTER_VALUE);
+    },
+    []
+  );
 
+  const handleVertexFilterChange = useCallback(
+    (e: SelectChangeEvent<string>) => {
+      setSelectedVertex(e.target.value);
+    },
+    []
+  );
+
+  const typeCounts = useMemo(() => {
     return (
       <ToggleButtonGroup
         value={typeFilter}
@@ -110,7 +142,7 @@ export function K8sEvents({
           >
             <span className="namespace-k8s-type-count-text">Normal</span>
             <div className="namespace-k8s-type-count-badge normal">
-              {data.normalCount}
+              {data?.normalCount || 0}
             </div>
           </Box>
         </ToggleButton>
@@ -128,7 +160,7 @@ export function K8sEvents({
           >
             <span className="namespace-k8s-type-count-text">Warning</span>
             <div className="namespace-k8s-type-count-badge warn">
-              {data.warningCount}
+              {data?.warningCount || 0}
             </div>
           </Box>
         </ToggleButton>
@@ -137,8 +169,38 @@ export function K8sEvents({
   }, [data, typeFilter, handleTypeFilterChange]);
 
   const table = useMemo(() => {
-    if (!data) {
-      return <Box>No Events</Box>;
+    if (loading) {
+      return (
+        <Box
+          sx={{
+            display: "flex",
+            justifyContent: "center",
+            alignItems: "center",
+            height: "100%",
+          }}
+        >
+          <CircularProgress />
+        </Box>
+      );
+    }
+    if (error) {
+      return (
+        <Box
+          sx={{
+            display: "flex",
+            flexDirection: "column",
+            height: "100%",
+            width: "100%",
+            justifyContent: "center",
+            alignItems: "center",
+          }}
+        >
+          <ErrorDisplay
+            title="Error loading events"
+            message={error}
+          />
+        </Box>
+      );
     }
     return (
       <Box
@@ -199,7 +261,178 @@ export function K8sEvents({
         </Box>
       </Box>
     );
-  }, [data, page, totalPages, filteredEvents, handlePageChange]);
+  }, [
+    error,
+    loading,
+    data,
+    page,
+    totalPages,
+    filteredEvents,
+    handlePageChange,
+  ]);
+
+  const filters = useMemo(() => {
+    const selectContainerStyle = {
+      display: "flex",
+      flexDirection: "column",
+      marginRight: "0.5rem",
+    };
+    const selectStyle = {
+      background: "#fff",
+      border: "1px solid #6B6C72",
+      height: "2.125rem",
+    };
+    if (vertexId) {
+      // Given vertex, no filters
+      return undefined;
+    }
+    if (pipelineId) {
+      // Pipeline given
+      if (
+        !vertexFilterOptions.has(pipelineId) ||
+        !vertexFilterOptions.get(pipelineId)?.length
+      ) {
+        // No vertex options
+        return undefined;
+      }
+      // Vertex options available
+      return (
+        <Box sx={selectContainerStyle}>
+          <label style={{ color: "#6B6C72" }}>Vertex</label>
+          <Select
+            label="Vertex"
+            defaultValue={DEFAULT_FILTER_VALUE}
+            value={selectedVertex}
+            inputProps={{
+              name: "Vertex",
+              id: "Vertex",
+            }}
+            style={selectStyle}
+            onChange={handleVertexFilterChange}
+          >
+            {(vertexFilterOptions.has(pipelineId)
+              ? [
+                  DEFAULT_FILTER_VALUE,
+                  ...(vertexFilterOptions.get(pipelineId) || []),
+                ]
+              : [DEFAULT_FILTER_VALUE]
+            ).map((pipeline) => (
+              <MenuItem key={pipeline} value={pipeline}>
+                {pipeline}
+              </MenuItem>
+            ))}
+          </Select>
+        </Box>
+      );
+    }
+    // No pipeline given
+    if (!pipelineFilterOptions.length) {
+      // No filter options
+      return undefined;
+    }
+    if (
+      !selectedPipeline ||
+      !vertexFilterOptions.has(selectedPipeline) ||
+      !vertexFilterOptions.get(selectedPipeline)?.length
+    ) {
+      // No pipeline selected or one selected and no vertex options, just show pipeline selector
+      return (
+        <Box sx={selectContainerStyle}>
+          <label style={{ color: "#6B6C72" }}>Pipeline</label>
+          <Select
+            label="Pipeline"
+            defaultValue={DEFAULT_FILTER_VALUE}
+            value={selectedPipeline}
+            inputProps={{
+              name: "Pipeline",
+              id: "Pipeline",
+            }}
+            style={selectStyle}
+            onChange={handlePipelineFilterChange}
+          >
+            {[DEFAULT_FILTER_VALUE, ...pipelineFilterOptions].map(
+              (pipeline) => (
+                <MenuItem key={pipeline} value={pipeline}>
+                  {pipeline}
+                </MenuItem>
+              )
+            )}
+          </Select>
+        </Box>
+      );
+    }
+    // Pipeline selected and vertex options available
+    return (
+      <Box
+        sx={{
+          display: "flex",
+          flexDirection: "row",
+        }}
+      >
+        <Box sx={selectContainerStyle}>
+          <label style={{ color: "#6B6C72" }}>Pipeline</label>
+          <Select
+            label="Pipeline"
+            defaultValue={DEFAULT_FILTER_VALUE}
+            value={selectedPipeline}
+            inputProps={{
+              name: "Pipeline",
+              id: "Pipeline",
+            }}
+            style={selectStyle}
+            onChange={handlePipelineFilterChange}
+          >
+            {[DEFAULT_FILTER_VALUE, ...pipelineFilterOptions].map(
+              (pipeline) => (
+                <MenuItem key={pipeline} value={pipeline}>
+                  {pipeline}
+                </MenuItem>
+              )
+            )}
+          </Select>
+        </Box>
+        <Box sx={selectContainerStyle}>
+          <label style={{ color: "#6B6C72" }}>Vertex</label>
+          <Select
+            label="Vertex"
+            defaultValue={DEFAULT_FILTER_VALUE}
+            value={selectedVertex}
+            inputProps={{
+              name: "Vertex",
+              id: "Vertex",
+            }}
+            style={selectStyle}
+            onChange={handleVertexFilterChange}
+          >
+            {(vertexFilterOptions.has(selectedPipeline)
+              ? [
+                  DEFAULT_FILTER_VALUE,
+                  ...(vertexFilterOptions.get(selectedPipeline) || []),
+                ]
+              : [DEFAULT_FILTER_VALUE]
+            ).map((pipeline) => (
+              <MenuItem key={pipeline} value={pipeline}>
+                {pipeline}
+              </MenuItem>
+            ))}
+          </Select>
+        </Box>
+      </Box>
+    );
+  }, [
+    vertexId,
+    pipelineId,
+    pipelineFilterOptions,
+    vertexFilterOptions,
+    selectedPipeline,
+    selectedVertex,
+    handlePipelineFilterChange,
+    handleVertexFilterChange,
+  ]);
+
+  if (vertexId && !pipelineId) {
+    return <Box>Missing Props</Box>;
+  }
 
   return (
     <Box
@@ -210,7 +443,7 @@ export function K8sEvents({
       }}
     >
       {!excludeHeader && (
-        <span className="namespace-k8s-title">K8s Events</span>
+        <span className="namespace-k8s-title">{headerText}</span>
       )}
       <Paper
         sx={{
@@ -218,19 +451,15 @@ export function K8sEvents({
           flexDirection: "column",
           padding: "1rem",
           marginTop: excludeHeader ? "0" : "2rem",
+          height: "100%",
         }}
         square={square}
         elevation={0}
       >
-        {loading && (
-          <Box sx={{ display: "flex", justifyContent: "center" }}>
-            <CircularProgress />
-          </Box>
-        )}
-        {error && (
-          <span className="namespace-k8s-error">{`Error loading events: ${error}`}</span>
-        )}
-        {typeCounts}
+        <Box sx={{ display: "flex", flexDirection: "row" }}>
+          {filters}
+          {typeCounts}
+        </Box>
         {table}
       </Paper>
     </Box>
diff --git a/ui/src/components/common/SlidingSidebar/partials/VertexDetails/index.tsx b/ui/src/components/common/SlidingSidebar/partials/VertexDetails/index.tsx
index ab110421..49c3ab54 100644
--- a/ui/src/components/common/SlidingSidebar/partials/VertexDetails/index.tsx
+++ b/ui/src/components/common/SlidingSidebar/partials/VertexDetails/index.tsx
@@ -284,7 +284,13 @@ export function VertexDetails({
         hidden={tabValue !== K8S_EVENTS_TAB_INDEX}
       >
         {tabValue === K8S_EVENTS_TAB_INDEX && (
-          <K8sEvents namespaceId={namespaceId} excludeHeader square />
+          <K8sEvents
+            namespaceId={namespaceId}
+            pipelineId={pipelineId}
+            vertexId={vertexId}
+            excludeHeader
+            square
+          />
         )}
       </div>
       {buffers && (
diff --git a/ui/src/components/common/SummaryPageLayout/index.tsx b/ui/src/components/common/SummaryPageLayout/index.tsx
index 6edc7354..00e1b240 100644
--- a/ui/src/components/common/SummaryPageLayout/index.tsx
+++ b/ui/src/components/common/SummaryPageLayout/index.tsx
@@ -276,13 +276,13 @@ export function SummaryPageLayout({
           zIndex: (theme) => theme.zIndex.drawer - 1,
           position: "fixed",
           top: "5.75rem",
-          padding: "0.5rem",
         }}
       >
         <Box
           sx={{
             display: "flex",
             width: "100%",
+            padding: "0rem",
           }}
         >
           {getSummaryComponent(summarySections)}
diff --git a/ui/src/components/common/SummaryPageLayout/partials/SummaryStatuses/index.tsx b/ui/src/components/common/SummaryPageLayout/partials/SummaryStatuses/index.tsx
index b2e598d5..39113755 100644
--- a/ui/src/components/common/SummaryPageLayout/partials/SummaryStatuses/index.tsx
+++ b/ui/src/components/common/SummaryPageLayout/partials/SummaryStatuses/index.tsx
@@ -1,6 +1,7 @@
 import React from "react";
 import Box from "@mui/material/Box";
 import { StatusBar } from "../../../StatusBar";
+import { Help } from "../../../Help";
 import circleCheck from "../../../../../images/checkmark-circle.png";
 import circleDash from "../../../../../images/circle-dash.png";
 
@@ -15,6 +16,7 @@ export interface SummaryStatusesProps {
   critical: number;
   activeText?: string;
   inAcitveText?: string;
+  tooltip?: string;
   linkComponent?: React.ReactNode;
 }
 
@@ -27,20 +29,28 @@ export function SummaryStatuses({
   critical,
   activeText = "Active",
   inAcitveText = "Non-Active",
+  tooltip,
   linkComponent,
 }: SummaryStatusesProps) {
   return (
     <Box
       sx={{
         display: "flex",
-        flexDirection: "column",
-        marginTop: "0.375rem",
+        flexDirection: "row",
         alignItems: "center",
         flexGrow: 1,
         justifyContent: "center",
       }}
     >
-      <Box sx={{ width: "fit-content" }}>
+      <Box
+        sx={{
+          width: "fit-content",
+          flexGrow: 1,
+          alignItems: "center",
+          justifyContent: "center",
+          paddingLeft: "1rem",
+        }}
+      >
         <span className="summary-statuses-title">{title}</span>
         <Box
           sx={{ display: "flex", flexDirection: "row", marginTop: "0.3125rem" }}
@@ -106,6 +116,21 @@ export function SummaryStatuses({
           )}
         </Box>
       </Box>
+      {tooltip && (
+        <Box
+          sx={{
+            display: "flex",
+            flexDirection: "row",
+            alignItems: "right",
+            justifyContent: "flex-end",
+            paddingRight: "0.2rem",
+            marginRight: "1rem",
+            height: "100%"
+          }}
+        >
+          <Help tooltip={tooltip} />
+        </Box>
+      )}
     </Box>
   );
 }
diff --git a/ui/src/components/common/SummaryPageLayout/partials/SummaryTitledValue/index.tsx b/ui/src/components/common/SummaryPageLayout/partials/SummaryTitledValue/index.tsx
index 4ff75dae..1d31e502 100644
--- a/ui/src/components/common/SummaryPageLayout/partials/SummaryTitledValue/index.tsx
+++ b/ui/src/components/common/SummaryPageLayout/partials/SummaryTitledValue/index.tsx
@@ -1,26 +1,55 @@
 import React from "react";
 import Box from "@mui/material/Box";
+import { Help } from "../../../Help";
 
 import "./style.css";
 
 export interface SummaryTitledValueProps {
   title: string;
   value: string | number;
+  tooltip?: string;
 }
 
-export function SummaryTitledValue({ title, value }: SummaryTitledValueProps) {
+export function SummaryTitledValue({
+  title,
+  value,
+  tooltip,
+}: SummaryTitledValueProps) {
   return (
     <Box
       sx={{
         display: "flex",
-        flexDirection: "column",
+        flexDirection: "row",
         alignItems: "center",
         flexGrow: 1,
         justifyContent: "center",
       }}
     >
-      <span className="summary-titled-value-title">{title}</span>
-      <span className="summary-titled-value-value">{value}</span>
+      <Box
+        sx={{
+          display: "flex",
+          flexDirection: "column",
+          flexGrow: 1,
+          paddingLeft: "1rem"
+        }}
+      >
+        <span className="summary-titled-value-title">{title}</span>
+        <span className="summary-titled-value-value">{value}</span>
+      </Box>
+      {tooltip && (
+        <Box
+          sx={{
+            display: "flex",
+            flexDirection: "row",
+            alignItems: "right",
+            height: "100%",
+            justifyContent: "flex-end",
+            marginRight: "1rem",
+          }}
+        >
+          <Help tooltip={tooltip} />
+        </Box>
+      )}
     </Box>
   );
 }
diff --git a/ui/src/components/pages/Cluster/index.tsx b/ui/src/components/pages/Cluster/index.tsx
index 7a6b4c5e..86bf6cd6 100644
--- a/ui/src/components/pages/Cluster/index.tsx
+++ b/ui/src/components/pages/Cluster/index.tsx
@@ -11,6 +11,11 @@ import { ErrorDisplay } from "../../common/ErrorDisplay";
 import { useClusterSummaryFetch } from "../../../utils/fetchWrappers/clusterSummaryFetch";
 import { AppContextProps } from "../../../types/declarations/app";
 import { AppContext } from "../../../App";
+import {
+  ISB_SERVICES_STATUS_TOOLTIP,
+  ISB_SERVICES_TOOLTIP,
+  PIPELINE_STATUS_TOOLTIP,
+} from "../../../utils";
 
 import "./style.css";
 
@@ -75,6 +80,7 @@ export function Cluster() {
               healthy: data.pipelinesHealthyCount,
               warning: data.pipelinesWarningCount,
               critical: data.pipelinesCriticalCount,
+              tooltip: PIPELINE_STATUS_TOOLTIP,
             },
           },
         ],
@@ -88,6 +94,7 @@ export function Cluster() {
             titledValueProps: {
               title: "ISB SERVICES",
               value: data.isbsCount,
+              tooltip: ISB_SERVICES_TOOLTIP,
             },
           },
           {
@@ -99,6 +106,7 @@ export function Cluster() {
               healthy: data.isbsHealthyCount,
               warning: data.isbsWarningCount,
               critical: data.isbsCriticalCount,
+              tooltip: ISB_SERVICES_STATUS_TOOLTIP,
             },
           },
         ],
diff --git a/ui/src/components/pages/Cluster/partials/ClusterNamespaceListing/index.tsx b/ui/src/components/pages/Cluster/partials/ClusterNamespaceListing/index.tsx
index a54159e2..ed1e9825 100644
--- a/ui/src/components/pages/Cluster/partials/ClusterNamespaceListing/index.tsx
+++ b/ui/src/components/pages/Cluster/partials/ClusterNamespaceListing/index.tsx
@@ -96,7 +96,7 @@ export function ClusterNamespaceListing({
             <Grid
               key={`ns-${ns.name}`}
               item
-              xl={3}
+              xl={4}
               lg={4}
               md={6}
               sm={12}
diff --git a/ui/src/components/pages/Cluster/partials/NamespaceCard/style.css b/ui/src/components/pages/Cluster/partials/NamespaceCard/style.css
index e06c41c2..7e11aa0a 100644
--- a/ui/src/components/pages/Cluster/partials/NamespaceCard/style.css
+++ b/ui/src/components/pages/Cluster/partials/NamespaceCard/style.css
@@ -3,6 +3,9 @@
   font-style: normal;
   font-weight: 400;
   color: #000;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
 }
 
 .namespace-card-section-title {
diff --git a/ui/src/components/pages/Namespace/index.tsx b/ui/src/components/pages/Namespace/index.tsx
index 9a7e4aea..9a7d6ffc 100644
--- a/ui/src/components/pages/Namespace/index.tsx
+++ b/ui/src/components/pages/Namespace/index.tsx
@@ -14,6 +14,10 @@ import { SidebarType } from "../../common/SlidingSidebar";
 import { NamespacePipelineListing } from "./partials/NamespacePipelineListing";
 import { ErrorDisplay } from "../../common/ErrorDisplay";
 import { NamespaceSummaryData } from "../../../types/declarations/namespace";
+import {
+  ISB_SERVICES_STATUS_TOOLTIP, ISB_SERVICES_TOOLTIP,
+  PIPELINE_STATUS_TOOLTIP,
+} from "../../../utils";
 
 import "./style.css";
 
@@ -49,15 +53,35 @@ export function Namespaces({ namespaceId: nsIdProp }: NamespaceProps) {
       loadOnRefresh: false,
       addError,
     });
+
   const handleK8sEventsClick = useCallback(() => {
     if (!namespaceId || !setSidebarProps) {
       return;
     }
+    const pipelines: string[] = [];
+    const vertexMap = new Map<string, string[]>();
+    if (pipelineRawData) {
+      Object.keys(pipelineRawData).forEach((pipelineId) => {
+        pipelines.push(pipelineId);
+        const vertices =
+          pipelineRawData[pipelineId]?.pipeline?.spec?.vertices || [];
+        vertices.forEach((vertex: any) => {
+          const listing = vertexMap.get(pipelineId) || [];
+          listing.push(vertex.name);
+          vertexMap.set(pipelineId, listing);
+        });
+      });
+    }
     setSidebarProps({
       type: SidebarType.NAMESPACE_K8s,
-      k8sEventsProps: { namespaceId },
+      k8sEventsProps: {
+        namespaceId,
+        pipelineFilterOptions: pipelines,
+        vertexFilterOptions: vertexMap,
+      },
     });
-  }, [namespaceId, setSidebarProps]);
+  }, [namespaceId, setSidebarProps, pipelineRawData]);
+
   const defaultPipelinesData = useMemo(() => {
     return [
       // Pipelines collection
@@ -80,6 +104,7 @@ export function Namespaces({ namespaceId: nsIdProp }: NamespaceProps) {
               healthy: 0,
               warning: 0,
               critical: 0,
+              tooltip: PIPELINE_STATUS_TOOLTIP,
             },
           },
         ],
@@ -93,6 +118,7 @@ export function Namespaces({ namespaceId: nsIdProp }: NamespaceProps) {
             titledValueProps: {
               title: "ISB SERVICES",
               value: 0,
+              tooltip: ISB_SERVICES_TOOLTIP
             },
           },
           {
@@ -104,6 +130,7 @@ export function Namespaces({ namespaceId: nsIdProp }: NamespaceProps) {
               healthy: 0,
               warning: 0,
               critical: 0,
+              tooltip: ISB_SERVICES_STATUS_TOOLTIP,
             },
           },
         ],
@@ -166,6 +193,7 @@ export function Namespaces({ namespaceId: nsIdProp }: NamespaceProps) {
               healthy: data.pipelinesHealthyCount,
               warning: data.pipelinesWarningCount,
               critical: data.pipelinesCriticalCount,
+              tooltip: PIPELINE_STATUS_TOOLTIP,
             },
           },
         ],
@@ -179,6 +207,7 @@ export function Namespaces({ namespaceId: nsIdProp }: NamespaceProps) {
             titledValueProps: {
               title: "ISB SERVICES",
               value: data.isbsCount,
+              tooltip: ISB_SERVICES_TOOLTIP
             },
           },
           {
@@ -190,6 +219,7 @@ export function Namespaces({ namespaceId: nsIdProp }: NamespaceProps) {
               healthy: data.isbsHealthyCount,
               warning: data.isbsWarningCount,
               critical: data.isbsCriticalCount,
+              tooltip: ISB_SERVICES_STATUS_TOOLTIP,
             },
           },
         ],
@@ -240,6 +270,7 @@ export function Namespaces({ namespaceId: nsIdProp }: NamespaceProps) {
         </Box>
       );
     }
+
     return (
       <NamespacePipelineListing
         namespace={namespaceId || ""}
diff --git a/ui/src/components/pages/Pipeline/index.tsx b/ui/src/components/pages/Pipeline/index.tsx
index 2da9d525..662a2187 100644
--- a/ui/src/components/pages/Pipeline/index.tsx
+++ b/ui/src/components/pages/Pipeline/index.tsx
@@ -1,4 +1,4 @@
-import { useCallback, useContext, useMemo, createContext } from "react";
+import React, { useCallback, useContext, useMemo, createContext } from "react";
 import { useParams } from "react-router-dom";
 import CircularProgress from "@mui/material/CircularProgress";
 import Box from "@mui/material/Box";
@@ -58,14 +58,26 @@ export function Pipeline({ namespaceId: nsIdProp }: PipelineProps) {
   }, [graphRefresh, summaryRefresh]);
 
   const handleK8sEventsClick = useCallback(() => {
-    if (!namespaceId || !setSidebarProps) {
+    if (!namespaceId || !pipelineId || !setSidebarProps) {
       return;
     }
+    const vertexMap = new Map<string, string[]>();
+    if (vertices?.length) {
+      vertexMap.set(
+        pipelineId,
+        vertices.map((v) => v.id)
+      );
+    }
     setSidebarProps({
       type: SidebarType.NAMESPACE_K8s,
-      k8sEventsProps: { namespaceId },
+      k8sEventsProps: {
+        namespaceId,
+        pipelineId,
+        headerText: "Pipeline K8s Events",
+        vertexFilterOptions: vertexMap,
+      },
     });
-  }, [namespaceId, setSidebarProps]);
+  }, [namespaceId, pipelineId, setSidebarProps, vertices]);
 
   const summarySections: SummarySection[] = useMemo(() => {
     if (summaryLoading) {
diff --git a/ui/src/components/pages/Pipeline/partials/Graph/index.tsx b/ui/src/components/pages/Pipeline/partials/Graph/index.tsx
index 50260a9a..d1461642 100644
--- a/ui/src/components/pages/Pipeline/partials/Graph/index.tsx
+++ b/ui/src/components/pages/Pipeline/partials/Graph/index.tsx
@@ -174,10 +174,22 @@ const Flow = (props: FlowProps) => {
     undefined
   );
   const [statusPayload, setStatusPayload] = useState<any>(undefined);
-  // eslint-disable-next-line @typescript-eslint/no-unused-vars
   const [timerDateStamp, setTimerDateStamp] = useState<any>(undefined);
-  const [timer, setTimer] = useState<any>(undefined);
+  const [timer, setTimer] = useState<number | undefined>(undefined);
 
+  const handleTimer = useCallback(() => {
+    if (timer) {
+      clearInterval(timer);
+    }
+    const dateString = new Date().toISOString();
+    const time = timeAgo(dateString);
+    setTimerDateStamp(time);
+    const pauseTimer = setInterval(() => {
+      const time = timeAgo(dateString);
+      setTimerDateStamp(time);
+    }, 1000);
+    setTimer(pauseTimer);
+  }, [timer]);
   const handlePlayClick = useCallback(() => {
     handleTimer();
     setStatusPayload({
@@ -187,7 +199,7 @@ const Flow = (props: FlowProps) => {
         },
       },
     });
-  }, []);
+  }, [handleTimer]);
 
   const handlePauseClick = useCallback(() => {
     handleTimer();
@@ -198,18 +210,7 @@ const Flow = (props: FlowProps) => {
         },
       },
     });
-  }, []);
-
-  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);
-  }, []);
+  }, [handleTimer]);
 
   useEffect(() => {
     const patchStatus = async () => {
@@ -245,14 +246,12 @@ const Flow = (props: FlowProps) => {
       statusPayload?.spec?.lifecycle?.desiredPhase === PAUSED &&
       data?.pipeline?.status?.phase === PAUSED
     ) {
-      clearInterval(timer);
       setStatusPayload(undefined);
     }
     if (
       statusPayload?.spec?.lifecycle?.desiredPhase === RUNNING &&
       data?.pipeline?.status?.phase === RUNNING
     ) {
-      clearInterval(timer);
       setStatusPayload(undefined);
     }
   }, [data]);
@@ -283,7 +282,8 @@ const Flow = (props: FlowProps) => {
       preventScrolling={!isLocked}
       panOnDrag={!isLocked}
       panOnScroll={isPanOnScrollLocked}
-      maxZoom={2.75}
+      minZoom={0.1}
+      maxZoom={3.1}
     >
       <Panel
         position="top-left"
@@ -440,7 +440,7 @@ const Flow = (props: FlowProps) => {
               stroke="#D4D7DC"
             />
             <text x="50%" y="50%" className={"zoom-percent-text"}>
-              {(((zoomLevel - 0.5) / 1.5) * 100).toFixed(0)}%
+              {(((zoomLevel - 0.1) / 2) * 100).toFixed(0)}%
             </text>
           </g>
         </svg>
diff --git a/ui/src/components/pages/Pipeline/partials/PipelineISBStatus/index.tsx b/ui/src/components/pages/Pipeline/partials/PipelineISBStatus/index.tsx
index ed927dad..5f329822 100644
--- a/ui/src/components/pages/Pipeline/partials/PipelineISBStatus/index.tsx
+++ b/ui/src/components/pages/Pipeline/partials/PipelineISBStatus/index.tsx
@@ -16,9 +16,8 @@ export function PipelineISBStatus({ isbData }) {
         display: "flex",
         flexDirection: "column",
         marginTop: "0.375rem",
-        alignItems: "center",
         flexGrow: 1,
-        justifyContent: "center",
+        paddingLeft: "1rem"
       }}
     >
       <Box sx={{ display: "flex", flexDirection: "row" }}>
diff --git a/ui/src/components/pages/Pipeline/partials/PipelineISBSummaryStatus/index.tsx b/ui/src/components/pages/Pipeline/partials/PipelineISBSummaryStatus/index.tsx
index 9019144f..5349be71 100644
--- a/ui/src/components/pages/Pipeline/partials/PipelineISBSummaryStatus/index.tsx
+++ b/ui/src/components/pages/Pipeline/partials/PipelineISBSummaryStatus/index.tsx
@@ -13,9 +13,8 @@ export function PipelineISBSummaryStatus({ isbData }) {
         display: "flex",
         flexDirection: "column",
         marginTop: "0.375rem",
-        alignItems: "center",
         flexGrow: 1,
-        justifyContent: "center",
+        paddingLeft: "1rem"
       }}
     >
       <Box sx={{ display: "flex", flexDirection: "row" }}>
diff --git a/ui/src/components/pages/Pipeline/partials/PipelineStatus/index.tsx b/ui/src/components/pages/Pipeline/partials/PipelineStatus/index.tsx
index e83d31a5..af3adfde 100644
--- a/ui/src/components/pages/Pipeline/partials/PipelineStatus/index.tsx
+++ b/ui/src/components/pages/Pipeline/partials/PipelineStatus/index.tsx
@@ -11,9 +11,8 @@ export function PipelineStatus({ status, healthStatus }) {
         display: "flex",
         flexDirection: "column",
         marginTop: "0.375rem",
-        alignItems: "center",
         flexGrow: 1,
-        justifyContent: "center",
+        paddingLeft: "1rem"
       }}
     >
       <Box sx={{ width: "fit-content" }}>
diff --git a/ui/src/components/pages/Pipeline/partials/PipelineSummaryStatus/index.tsx b/ui/src/components/pages/Pipeline/partials/PipelineSummaryStatus/index.tsx
index 69c39b54..50b8a89b 100644
--- a/ui/src/components/pages/Pipeline/partials/PipelineSummaryStatus/index.tsx
+++ b/ui/src/components/pages/Pipeline/partials/PipelineSummaryStatus/index.tsx
@@ -4,7 +4,11 @@ import { useParams } from "react-router-dom";
 import { SidebarType } from "../../../../common/SlidingSidebar";
 import { AppContextProps } from "../../../../../types/declarations/app";
 import { AppContext } from "../../../../../App";
-import { DurationString } from "../../../../../utils";
+import {
+  DurationString,
+  HTMLlTooltip,
+  MAX_LAG_TOOLTIP,
+} from "../../../../../utils";
 import { ViewType } from "../../../../common/SpecEditor";
 
 import "./style.css";
@@ -51,9 +55,8 @@ export function PipelineSummaryStatus({ pipelineId, pipeline, lag, refresh }) {
         display: "flex",
         flexDirection: "column",
         marginTop: "0.375rem",
-        alignItems: "center",
         flexGrow: 1,
-        justifyContent: "center",
+        paddingLeft: "1rem"
       }}
     >
       <Box sx={{ width: "fit-content" }}>
@@ -102,18 +105,24 @@ export function PipelineSummaryStatus({ pipelineId, pipeline, lag, refresh }) {
             sx={{
               display: "flex",
               flexDirection: "column",
-              width: "12rem"
+              width: "12rem",
             }}
           >
             <div className="pipeline-summary-text">
               <span className="pipeline-summary-subtitle">
-                <div>
-                  <span className="pipeline-summary-subtitle">Max lag:</span>
-                  <span className="pipeline-summary-text">
-                    {" "}
-                    {DurationString(lag)}
-                  </span>
-                </div>
+                <HTMLlTooltip
+                  title={MAX_LAG_TOOLTIP}
+                  placement="top-start"
+                  arrow
+                >
+                  <div>
+                    <span className="pipeline-summary-subtitle">Max lag:</span>
+                    <span className="pipeline-summary-text">
+                      {" "}
+                      {lag < 0 ? "Unavailable now" : DurationString(lag)}
+                    </span>
+                  </div>
+                </HTMLlTooltip>
               </span>
             </div>
             <div className="pipeline-summary-text">
@@ -121,6 +130,7 @@ export function PipelineSummaryStatus({ pipelineId, pipeline, lag, refresh }) {
                 <div
                   className="pipeline-onclick-events"
                   onClick={handleSpecClick}
+                  data-testid="pipeline-spec-click"
                 >
                   View/Edit Specs
                 </div>
diff --git a/ui/src/types/declarations/namespace.d.ts b/ui/src/types/declarations/namespace.d.ts
index 275b41e8..ea705e58 100644
--- a/ui/src/types/declarations/namespace.d.ts
+++ b/ui/src/types/declarations/namespace.d.ts
@@ -70,6 +70,8 @@ export interface K8sEventSummary {
 }
 export interface NamespaceK8sEventsFetchProps {
   namespace: string;
+  pipeline?: string;
+  vertex?: string;
 }
 export interface NamespaceK8sEventsFetchResult {
   data?: K8sEventSummary;
diff --git a/ui/src/utils/fetchWrappers/namespaceK8sEventsFetch.ts b/ui/src/utils/fetchWrappers/namespaceK8sEventsFetch.ts
index 34561429..c25c3d1b 100644
--- a/ui/src/utils/fetchWrappers/namespaceK8sEventsFetch.ts
+++ b/ui/src/utils/fetchWrappers/namespaceK8sEventsFetch.ts
@@ -1,4 +1,4 @@
-import { useEffect, useState } from "react";
+import { useEffect, useState, useMemo } from "react";
 import { useFetch, Options } from "./fetch";
 import {
   K8sEvent,
@@ -7,40 +7,6 @@ import {
   NamespaceK8sEventsFetchResult,
 } from "../../types/declarations/namespace";
 
-// const MOCK_DATA = [
-//   {
-//     apiVersion: "v1",
-//     count: 4,
-//     eventTime: null,
-//     firstTimestamp: "2023-09-11T19:16:47Z",
-//     involvedObject: {
-//       apiVersion: "iammanager.keikoproj.io/v1alpha1",
-//       kind: "Iamrole",
-//       name: "iamrole",
-//       namespace: "dev-log-usw2-dev",
-//       resourceVersion: "16504395",
-//       uid: "517e486c-a9a0-4cf7-bbdd-0269e12fe8b8",
-//     },
-//     kind: "Event",
-//     lastTimestamp: "2023-09-11T21:15:45Z",
-//     message: "Successfully created/updated iam role",
-//     metadata: {
-//       creationTimestamp: "2023-09-11T21:15:45Z",
-//       name: "iamrole.1783ee37b9ff4d70",
-//       namespace: "dev-log-usw2-dev",
-//       resourceVersion: "16504414",
-//       uid: "a522d798-1bb5-4f46-aae0-71385e87fcd7",
-//     },
-//     reason: "Ready",
-//     reportingComponent: "",
-//     reportingInstance: "",
-//     source: {
-//       component: "iam-manager",
-//     },
-//     type: "Normal",
-//   },
-// ];
-
 const rawDataToEventListing = (
   namespace: string,
   rawData: any[]
@@ -81,6 +47,8 @@ const rawDataToEventListing = (
 
 export const useNamespaceK8sEventsFetch = ({
   namespace,
+  pipeline,
+  vertex,
 }: NamespaceK8sEventsFetchProps) => {
   const [results, setResults] = useState<NamespaceK8sEventsFetchResult>({
     data: undefined,
@@ -91,11 +59,21 @@ export const useNamespaceK8sEventsFetch = ({
     skip: false,
     requestKey: "",
   });
+
+  const urlPath = useMemo(() => {
+    if (vertex) {
+      return `/api/v1/namespaces/${namespace}/events?objectType=vertex&objectName=${pipeline}-${vertex}`;
+    } else if (pipeline) {
+      return `/api/v1/namespaces/${namespace}/events?objectType=pipeline&objectName=${pipeline}`;
+    }
+    return `/api/v1/namespaces/${namespace}/events`;
+  }, [namespace, pipeline, vertex])
+
   const {
     data: fetchData,
     loading: fetchLoading,
     error: fetchError,
-  } = useFetch(`/api/v1/namespaces/${namespace}/events`, undefined, options);
+  } = useFetch(urlPath, undefined, options);
 
   useEffect(() => {
     if (fetchLoading) {
diff --git a/ui/src/utils/index.ts b/ui/src/utils/index.tsx
similarity index 85%
rename from ui/src/utils/index.ts
rename to ui/src/utils/index.tsx
index d7a950a6..6f8944d5 100644
--- a/ui/src/utils/index.ts
+++ b/ui/src/utils/index.tsx
@@ -1,3 +1,4 @@
+import React from "react";
 import { Pod, PodDetail, ResourceUsage } from "../types/declarations/pods";
 import circleCheck from "../../src/images/checkmark-circle.png";
 import circleDash from "../../src/images/circle-dash.png";
@@ -6,6 +7,8 @@ import warning from "../../src/images/warning-circle.png";
 import critical from "../../src/images/critical.png";
 import moment from "moment";
 import { IsbServiceSpec } from "../types/declarations/pipeline";
+import { styled } from "@mui/material/styles";
+import Tooltip, { tooltipClasses, TooltipProps } from "@mui/material/Tooltip";
 
 // global constants
 export const ALL = "All";
@@ -51,7 +54,8 @@ export async function getAPIResponseError(
       try {
         const data = await response.json();
         if (data.errMsg) {
-          message = response.status === 403 ? `Error: ${data.errMsg}` : data.errMsg;
+          message =
+            response.status === 403 ? `Error: ${data.errMsg}` : data.errMsg;
         }
       } catch (e) {
         // Ignore
@@ -342,3 +346,37 @@ export const timeAgo = (timestamp: string) => {
     }
   return time;
 };
+
+export const PIPELINE_STATUS_TOOLTIP =
+  "Pipeline health is determined by backpressure. Active pipelines are running, while non-active pipelines are failed, pausing, paused, or deleting.";
+export const ISB_SERVICES_TOOLTIP =
+  "Inter State Buffer Services are used to transfer data between vertices in a pipeline.";
+export const ISB_SERVICES_STATUS_TOOLTIP = (
+  <React.Fragment>
+    <>
+      <b>Healthy:</b>&nbsp; The ISB service is operating optimally. No issues or
+      anomalies detected.
+      <hr />
+      <b>Warning:</b>&nbsp; The ISB service is experiencing minor issues or
+      degradation within the data processing pipeline. Consider monitoring and
+      further investigation.
+      <hr />
+      <b>Critical:</b>&nbsp; The ISB service is in a critical state. Immediate
+      attention required.
+    </>
+  </React.Fragment>
+);
+export const MAX_LAG_TOOLTIP =
+  "Max lag is the amount of time between the oldest and newest data that is being processed on.";
+export const ERRORS_TOOLTIP =
+  "Errors that occur when attempting to retrieve data";
+// export const PIPELINES_TOOLTIP = "Pipelines list"
+// export const ISB_TOOLTIP = "Inter State Buffer services list"
+
+export const HTMLlTooltip = styled(({ className, ...props }: TooltipProps) => (
+  <Tooltip {...props} classes={{ popper: className }} />
+))(() => ({
+  [`& .${tooltipClasses.tooltip}`]: {
+    fontSize: 14,
+  },
+}));


Test patch:
diff --git a/pkg/daemon/server/service/rater/goleak_test.go b/pkg/daemon/server/service/rater/goleak_test.go
index fb234536..58abf6f6 100644
--- a/pkg/daemon/server/service/rater/goleak_test.go
+++ b/pkg/daemon/server/service/rater/goleak_test.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package server
+package rater
 
 import (
 	"testing"
diff --git a/pkg/daemon/server/service/rater/helper_test.go b/pkg/daemon/server/service/rater/helper_test.go
index e2f00388..e420bcc0 100644
--- a/pkg/daemon/server/service/rater/helper_test.go
+++ b/pkg/daemon/server/service/rater/helper_test.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package server
+package rater
 
 import (
 	"testing"
diff --git a/pkg/daemon/server/service/rater/pod_tracker_test.go b/pkg/daemon/server/service/rater/pod_tracker_test.go
index 203645e9..99974345 100644
--- a/pkg/daemon/server/service/rater/pod_tracker_test.go
+++ b/pkg/daemon/server/service/rater/pod_tracker_test.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package server
+package rater
 
 import (
 	"bytes"
@@ -123,3 +123,20 @@ func TestPodTracker_Start(t *testing.T) {
 	assert.Equal(t, true, tracker.IsActive("p*v*4"))
 	assert.Equal(t, false, tracker.IsActive("p*v*5"))
 }
+
+func TestPodTracker_GetPodInfo(t *testing.T) {
+	ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
+	defer cancel()
+	tracker := NewPodTracker(ctx, nil, WithRefreshInterval(time.Second))
+	// error scenario - more than 3 fields
+	podInfo, err := tracker.GetPodInfo("p*v*0*1")
+	assert.Nilf(t, podInfo, "podInfo should be nil")
+	assert.ErrorContains(t, err, "invalid key")
+
+	// common scenario - get the pod info
+	podInfo, err = tracker.GetPodInfo("p*v*0")
+	assert.Nilf(t, err, "error should be nil")
+	assert.Equal(t, "p", podInfo.pipelineName)
+	assert.Equal(t, "v", podInfo.vertexName)
+	assert.Equal(t, "p-v-0", podInfo.podName)
+}
diff --git a/pkg/daemon/server/service/rater/rater_test.go b/pkg/daemon/server/service/rater/rater_test.go
index b33dbf8e..e6f6fae0 100644
--- a/pkg/daemon/server/service/rater/rater_test.go
+++ b/pkg/daemon/server/service/rater/rater_test.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package server
+package rater
 
 import (
 	"bytes"
diff --git a/pkg/daemon/server/service/rater/timestamped_counts_test.go b/pkg/daemon/server/service/rater/timestamped_counts_test.go
index 048874d8..4220ab98 100644
--- a/pkg/daemon/server/service/rater/timestamped_counts_test.go
+++ b/pkg/daemon/server/service/rater/timestamped_counts_test.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package server
+package rater
 
 import (
 	"testing"
diff --git a/pkg/daemon/server/service/rater/uniq_str_list_test.go b/pkg/daemon/server/service/rater/uniq_str_list_test.go
index 99ccb861..94f958e0 100644
--- a/pkg/daemon/server/service/rater/uniq_str_list_test.go
+++ b/pkg/daemon/server/service/rater/uniq_str_list_test.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package server
+package rater
 
 import (
 	"testing"
diff --git a/pkg/reduce/pbq/pbq_test.go b/pkg/reduce/pbq/pbq_test.go
index 2ccdd582..dc6754d1 100644
--- a/pkg/reduce/pbq/pbq_test.go
+++ b/pkg/reduce/pbq/pbq_test.go
@@ -22,9 +22,10 @@ import (
 	"testing"
 	"time"
 
-	"github.com/numaproj/numaflow/pkg/window/keyed"
 	"github.com/stretchr/testify/assert"
 
+	"github.com/numaproj/numaflow/pkg/window/keyed"
+
 	"github.com/numaproj/numaflow/pkg/isb"
 	"github.com/numaproj/numaflow/pkg/isb/testutils"
 	"github.com/numaproj/numaflow/pkg/reduce/pbq/partition"
@@ -58,7 +59,7 @@ func TestPBQ_ReadWrite(t *testing.T) {
 	kwOne := keyed.NewKeyedWindow(time.Unix(60, 0), time.Unix(120, 0))
 	kwOne.AddSlot("slot-1")
 
-	pq, err := qManager.CreateNewPBQ(ctx, partitionID, kwOne)
+	pq, err := qManager.CreateNewPBQ(ctx, partitionID)
 	assert.NoError(t, err)
 
 	var readMessages []*isb.ReadMessage
@@ -125,7 +126,7 @@ func Test_PBQReadWithCanceledContext(t *testing.T) {
 	kwOne.AddSlot("slot-1")
 
 	var pq ReadWriteCloser
-	pq, err = qManager.CreateNewPBQ(ctx, partitionID, kwOne)
+	pq, err = qManager.CreateNewPBQ(ctx, partitionID)
 	assert.NoError(t, err)
 
 	var readMessages []*isb.ReadMessage
@@ -193,7 +194,7 @@ func TestPBQ_WriteWithStoreFull(t *testing.T) {
 	kwOne.AddSlot("slot-1")
 
 	var pq ReadWriteCloser
-	pq, err = qManager.CreateNewPBQ(ctx, partitionID, kwOne)
+	pq, err = qManager.CreateNewPBQ(ctx, partitionID)
 	assert.NoError(t, err)
 
 	for _, msg := range writeMessages {
diff --git a/pkg/reduce/pbq/pbqmanager_test.go b/pkg/reduce/pbq/pbqmanager_test.go
index 96361d11..ac211986 100644
--- a/pkg/reduce/pbq/pbqmanager_test.go
+++ b/pkg/reduce/pbq/pbqmanager_test.go
@@ -22,9 +22,10 @@ import (
 	"testing"
 	"time"
 
-	"github.com/numaproj/numaflow/pkg/window/keyed"
 	"github.com/stretchr/testify/assert"
 
+	"github.com/numaproj/numaflow/pkg/window/keyed"
+
 	"github.com/numaproj/numaflow/pkg/isb"
 	"github.com/numaproj/numaflow/pkg/isb/testutils"
 	"github.com/numaproj/numaflow/pkg/reduce/pbq/partition"
@@ -54,17 +55,11 @@ func TestManager_ListPartitions(t *testing.T) {
 		Slot:  "slot-2",
 	}
 
-	kwOne := keyed.NewKeyedWindow(time.Unix(60, 0), time.Unix(120, 0))
-	kwOne.AddSlot("slot-1")
-
-	kwTwo := keyed.NewKeyedWindow(time.Unix(60, 0), time.Unix(120, 0))
-	kwOne.AddSlot("slot-2")
-
 	var pq1, pq2 ReadWriteCloser
-	pq1, err = pbqManager.CreateNewPBQ(ctx, testPartition, kwOne)
+	pq1, err = pbqManager.CreateNewPBQ(ctx, testPartition)
 	assert.NoError(t, err)
 
-	pq2, err = pbqManager.CreateNewPBQ(ctx, partitionTwo, kwTwo)
+	pq2, err = pbqManager.CreateNewPBQ(ctx, partitionTwo)
 	assert.NoError(t, err)
 
 	assert.Len(t, pbqManager.ListPartitions(), 2)
@@ -97,7 +92,7 @@ func TestManager_GetPBQ(t *testing.T) {
 	kwOne := keyed.NewKeyedWindow(time.Unix(60, 0), time.Unix(120, 0))
 	kwOne.AddSlot("slot-1")
 
-	pb1, err = pbqManager.CreateNewPBQ(ctx, testPartition, kwOne)
+	pb1, err = pbqManager.CreateNewPBQ(ctx, testPartition)
 	assert.NoError(t, err)
 
 	// get the created pbq
@@ -125,7 +120,7 @@ func TestPBQFlow(t *testing.T) {
 	kwOne.AddSlot("slot-1")
 
 	var pq ReadWriteCloser
-	pq, err = pbqManager.CreateNewPBQ(ctx, testPartition, kwOne)
+	pq, err = pbqManager.CreateNewPBQ(ctx, testPartition)
 	assert.NoError(t, err)
 
 	msgsCount := 5
@@ -189,7 +184,7 @@ func TestPBQFlowWithNoOpStore(t *testing.T) {
 
 	// create a pbq backed with no op store
 	var pq ReadWriteCloser
-	pq, err = pbqManager.CreateNewPBQ(ctx, testPartition, kwOne)
+	pq, err = pbqManager.CreateNewPBQ(ctx, testPartition)
 	msgsCount := 50
 	var wg sync.WaitGroup
 
@@ -249,7 +244,7 @@ func TestManager_Replay(t *testing.T) {
 	kwOne.AddSlot("slot-1")
 
 	var pq ReadWriteCloser
-	pq, err = pbqManager.CreateNewPBQ(ctx, testPartition, kwOne)
+	pq, err = pbqManager.CreateNewPBQ(ctx, testPartition)
 	assert.NoError(t, err)
 	var wg sync.WaitGroup
 	wg.Add(1)
@@ -351,29 +346,17 @@ func TestManager_NextWindowToBeClosed(t *testing.T) {
 		Slot:  "slot-2",
 	}
 
-	kwOne := keyed.NewKeyedWindow(time.Unix(60, 0), time.Unix(120, 0))
-	kwOne.AddSlot("slot-1")
-
-	kwTwo := keyed.NewKeyedWindow(time.Unix(60, 0), time.Unix(120, 0))
-	kwOne.AddSlot("slot-2")
-
-	kwThree := keyed.NewKeyedWindow(time.Unix(120, 0), time.Unix(240, 0))
-	kwOne.AddSlot("slot-1")
-
-	kwFour := keyed.NewKeyedWindow(time.Unix(120, 0), time.Unix(240, 0))
-	kwOne.AddSlot("slot-2")
-
 	var pq1, pq2, pq3, pq4 ReadWriteCloser
-	pq1, err = pbqManager.CreateNewPBQ(ctx, partitionOne, kwOne)
+	pq1, err = pbqManager.CreateNewPBQ(ctx, partitionOne)
 	assert.NoError(t, err)
 
-	pq2, err = pbqManager.CreateNewPBQ(ctx, partitionTwo, kwTwo)
+	pq2, err = pbqManager.CreateNewPBQ(ctx, partitionTwo)
 	assert.NoError(t, err)
 
-	pq3, err = pbqManager.CreateNewPBQ(ctx, partitionThree, kwThree)
+	pq3, err = pbqManager.CreateNewPBQ(ctx, partitionThree)
 	assert.NoError(t, err)
 
-	pq4, err = pbqManager.CreateNewPBQ(ctx, partitionFour, kwFour)
+	pq4, err = pbqManager.CreateNewPBQ(ctx, partitionFour)
 	assert.NoError(t, err)
 
 	aw := pbqManager.NextWindowToBeMaterialized()
diff --git a/pkg/reduce/pnf/ordered_test.go b/pkg/reduce/pnf/ordered_test.go
index 16560290..9ae6c3fc 100644
--- a/pkg/reduce/pnf/ordered_test.go
+++ b/pkg/reduce/pnf/ordered_test.go
@@ -130,7 +130,7 @@ func TestOrderedProcessing(t *testing.T) {
 				kw := keyed.NewKeyedWindow(_partition.Start, _partition.End)
 				kw.AddSlot(_partition.Slot)
 
-				_, _ = pbqManager.CreateNewPBQ(ctx, _partition, kw)
+				_, _ = pbqManager.CreateNewPBQ(ctx, _partition)
 				t := op.SchedulePnF(cCtx, _partition)
 				op.InsertTask(t)
 			}
diff --git a/pkg/reduce/pnf/processandforward_test.go b/pkg/reduce/pnf/processandforward_test.go
index bd7650b5..d6247eda 100644
--- a/pkg/reduce/pnf/processandforward_test.go
+++ b/pkg/reduce/pnf/processandforward_test.go
@@ -124,7 +124,7 @@ func TestProcessAndForward_Process(t *testing.T) {
 
 	// create a pbq for a partition
 	var simplePbq pbq.ReadWriteCloser
-	simplePbq, err = pbqManager.CreateNewPBQ(ctx, testPartition, kw)
+	simplePbq, err = pbqManager.CreateNewPBQ(ctx, testPartition)
 	assert.NoError(t, err)
 
 	// write messages to pbq
@@ -401,7 +401,7 @@ func createProcessAndForwardAndOTStore(ctx context.Context, key string, pbqManag
 	// create a pbq for a partition
 	pw, otStore := buildPublisherMapAndOTStore(toBuffers)
 	var simplePbq pbq.Reader
-	simplePbq, _ = pbqManager.CreateNewPBQ(ctx, testPartition, kw)
+	simplePbq, _ = pbqManager.CreateNewPBQ(ctx, testPartition)
 
 	resultPayload, _ := json.Marshal(PayloadForTest{
 		Key:   "result_payload",
diff --git a/ui/src/components/pages/Pipeline/partials/PipelineISBStatus/index.test.tsx b/ui/src/components/pages/Pipeline/partials/PipelineISBStatus/index.test.tsx
new file mode 100644
index 00000000..eb6ed45f
--- /dev/null
+++ b/ui/src/components/pages/Pipeline/partials/PipelineISBStatus/index.test.tsx
@@ -0,0 +1,196 @@
+import React from "react";
+import { render, screen } from "@testing-library/react";
+import "@testing-library/jest-dom";
+import { AppContext } from "../../../../../App";
+import { BrowserRouter } from "react-router-dom";
+import { PipelineISBStatus } from "./index";
+
+const mockData = {
+  name: "default",
+  status: "healthy",
+  isbService: {
+    kind: "InterStepBufferService",
+    apiVersion: "v1alpha1",
+    metadata: {
+      name: "default",
+      namespace: "numaflow-system",
+      uid: "55786992-26ce-47d8-860e-2aece99f8ba8",
+      resourceVersion: "368601",
+      generation: 1,
+      creationTimestamp: "2023-10-31T14:13:25Z",
+      annotations: {
+        "kubectl.kubernetes.io/last-applied-configuration":
+          '{"apiVersion":"numaflow.numaproj.io/v1alpha1","kind":"InterStepBufferService","metadata":{"annotations":{},"name":"default","namespace":"numaflow-system"},"spec":{"jetstream":{"persistence":{"volumeSize":"3Gi"},"version":"latest"}}}\n',
+      },
+      finalizers: ["isbsvc-controller"],
+      managedFields: [
+        {
+          manager: "kubectl-client-side-apply",
+          operation: "Update",
+          apiVersion: "numaflow.numaproj.io/v1alpha1",
+          time: "2023-10-31T14:13:25Z",
+          fieldsType: "FieldsV1",
+          fieldsV1: {
+            "f:metadata": {
+              "f:annotations": {
+                ".": {},
+                "f:kubectl.kubernetes.io/last-applied-configuration": {},
+              },
+            },
+            "f:spec": {
+              ".": {},
+              "f:jetstream": {
+                ".": {},
+                "f:persistence": {
+                  ".": {},
+                  "f:volumeSize": {},
+                },
+                "f:replicas": {},
+                "f:version": {},
+              },
+            },
+          },
+        },
+        {
+          manager: "numaflow",
+          operation: "Update",
+          apiVersion: "numaflow.numaproj.io/v1alpha1",
+          time: "2023-10-31T14:13:27Z",
+          fieldsType: "FieldsV1",
+          fieldsV1: {
+            "f:metadata": {
+              "f:finalizers": {
+                ".": {},
+                'v:"isbsvc-controller"': {},
+              },
+            },
+          },
+        },
+        {
+          manager: "numaflow",
+          operation: "Update",
+          apiVersion: "numaflow.numaproj.io/v1alpha1",
+          time: "2023-10-31T14:13:27Z",
+          fieldsType: "FieldsV1",
+          fieldsV1: {
+            "f:status": {
+              ".": {},
+              "f:conditions": {},
+              "f:config": {
+                ".": {},
+                "f:jetstream": {
+                  ".": {},
+                  "f:auth": {
+                    ".": {},
+                    "f:basic": {
+                      ".": {},
+                      "f:password": {
+                        ".": {},
+                        "f:key": {},
+                        "f:name": {},
+                      },
+                      "f:user": {
+                        ".": {},
+                        "f:key": {},
+                        "f:name": {},
+                      },
+                    },
+                  },
+                  "f:streamConfig": {},
+                  "f:url": {},
+                },
+              },
+              "f:phase": {},
+              "f:type": {},
+            },
+          },
+          subresource: "status",
+        },
+      ],
+    },
+    spec: {
+      jetstream: {
+        version: "latest",
+        replicas: 3,
+        persistence: {
+          volumeSize: "3Gi",
+        },
+      },
+    },
+    status: {
+      conditions: [
+        {
+          type: "Configured",
+          status: "True",
+          lastTransitionTime: "2023-10-31T14:13:27Z",
+          reason: "Successful",
+          message: "Successful",
+        },
+        {
+          type: "Deployed",
+          status: "True",
+          lastTransitionTime: "2023-10-31T14:13:27Z",
+          reason: "Successful",
+          message: "Successful",
+        },
+      ],
+      phase: "Running",
+      config: {
+        jetstream: {
+          url: "nats://isbsvc-default-js-svc.numaflow-system.svc:4222",
+          auth: {
+            basic: {
+              user: {
+                name: "isbsvc-default-js-client-auth",
+                key: "client-auth-user",
+              },
+              password: {
+                name: "isbsvc-default-js-client-auth",
+                key: "client-auth-password",
+              },
+            },
+          },
+          streamConfig:
+            "consumer:\n  ackwait: 60s\n  maxackpending: 25000\notbucket:\n  history: 1\n  maxbytes: 0\n  maxvaluesize: 0\n  replicas: 3\n  storage: 0\n  ttl: 3h\nprocbucket:\n  history: 1\n  maxbytes: 0\n  maxvaluesize: 0\n  replicas: 3\n  storage: 0\n  ttl: 72h\nstream:\n  duplicates: 60s\n  maxage: 72h\n  maxbytes: -1\n  maxmsgs: 100000\n  replicas: 3\n  retention: 0\n  storage: 0\n",
+        },
+      },
+      type: "jetstream",
+    },
+  },
+};
+
+describe("PipelineISBStatus", () => {
+  it("should render the component", () => {
+    render(
+      <AppContext.Provider
+        value={{
+          isbData: mockData,
+        }}
+      >
+        <BrowserRouter>
+          <PipelineISBStatus isbData={mockData} />
+        </BrowserRouter>
+      </AppContext.Provider>
+    );
+    expect(screen.getByText("ISB SERVICES STATUS")).toBeInTheDocument();
+    expect(screen.getByText("Active")).toBeInTheDocument();
+    expect(screen.getByText("Healthy")).toBeInTheDocument();
+  });
+
+  it("Should render correctly when isbData is null", () => {
+    render(
+      <AppContext.Provider
+        value={{
+          isbData: null,
+        }}
+      >
+        <BrowserRouter>
+          <PipelineISBStatus isbData={null} />
+        </BrowserRouter>
+      </AppContext.Provider>
+    );
+    expect(screen.getByText("ISB SERVICES STATUS")).toBeInTheDocument();
+    expect(screen.getByText("Unknown")).toBeInTheDocument();
+    expect(screen.getByText("Unknown")).toBeInTheDocument();
+  });
+});
diff --git a/ui/src/components/pages/Pipeline/partials/PipelineISBSummaryStatus/index.test.tsx b/ui/src/components/pages/Pipeline/partials/PipelineISBSummaryStatus/index.test.tsx
new file mode 100644
index 00000000..ea0ff2f4
--- /dev/null
+++ b/ui/src/components/pages/Pipeline/partials/PipelineISBSummaryStatus/index.test.tsx
@@ -0,0 +1,187 @@
+import React from "react";
+import { render, screen } from "@testing-library/react";
+import "@testing-library/jest-dom";
+import { AppContext } from "../../../../../App";
+import { BrowserRouter } from "react-router-dom";
+import { PipelineISBSummaryStatus } from "./index";
+
+const mockData = {
+  name: "default",
+  status: "healthy",
+  isbService: {
+    kind: "InterStepBufferService",
+    apiVersion: "v1alpha1",
+    metadata: {
+      name: "default",
+      namespace: "numaflow-system",
+      uid: "55786992-26ce-47d8-860e-2aece99f8ba8",
+      resourceVersion: "368601",
+      generation: 1,
+      creationTimestamp: "2023-10-31T14:13:25Z",
+      annotations: {
+        "kubectl.kubernetes.io/last-applied-configuration":
+          '{"apiVersion":"numaflow.numaproj.io/v1alpha1","kind":"InterStepBufferService","metadata":{"annotations":{},"name":"default","namespace":"numaflow-system"},"spec":{"jetstream":{"persistence":{"volumeSize":"3Gi"},"version":"latest"}}}\n',
+      },
+      finalizers: ["isbsvc-controller"],
+      managedFields: [
+        {
+          manager: "kubectl-client-side-apply",
+          operation: "Update",
+          apiVersion: "numaflow.numaproj.io/v1alpha1",
+          time: "2023-10-31T14:13:25Z",
+          fieldsType: "FieldsV1",
+          fieldsV1: {
+            "f:metadata": {
+              "f:annotations": {
+                ".": {},
+                "f:kubectl.kubernetes.io/last-applied-configuration": {},
+              },
+            },
+            "f:spec": {
+              ".": {},
+              "f:jetstream": {
+                ".": {},
+                "f:persistence": {
+                  ".": {},
+                  "f:volumeSize": {},
+                },
+                "f:replicas": {},
+                "f:version": {},
+              },
+            },
+          },
+        },
+        {
+          manager: "numaflow",
+          operation: "Update",
+          apiVersion: "numaflow.numaproj.io/v1alpha1",
+          time: "2023-10-31T14:13:27Z",
+          fieldsType: "FieldsV1",
+          fieldsV1: {
+            "f:metadata": {
+              "f:finalizers": {
+                ".": {},
+                'v:"isbsvc-controller"': {},
+              },
+            },
+          },
+        },
+        {
+          manager: "numaflow",
+          operation: "Update",
+          apiVersion: "numaflow.numaproj.io/v1alpha1",
+          time: "2023-10-31T14:13:27Z",
+          fieldsType: "FieldsV1",
+          fieldsV1: {
+            "f:status": {
+              ".": {},
+              "f:conditions": {},
+              "f:config": {
+                ".": {},
+                "f:jetstream": {
+                  ".": {},
+                  "f:auth": {
+                    ".": {},
+                    "f:basic": {
+                      ".": {},
+                      "f:password": {
+                        ".": {},
+                        "f:key": {},
+                        "f:name": {},
+                      },
+                      "f:user": {
+                        ".": {},
+                        "f:key": {},
+                        "f:name": {},
+                      },
+                    },
+                  },
+                  "f:streamConfig": {},
+                  "f:url": {},
+                },
+              },
+              "f:phase": {},
+              "f:type": {},
+            },
+          },
+          subresource: "status",
+        },
+      ],
+    },
+    spec: {
+      jetstream: {
+        version: "latest",
+        replicas: 3,
+        persistence: {
+          volumeSize: "3Gi",
+        },
+      },
+    },
+    status: {
+      conditions: [
+        {
+          type: "Configured",
+          status: "True",
+          lastTransitionTime: "2023-10-31T14:13:27Z",
+          reason: "Successful",
+          message: "Successful",
+        },
+        {
+          type: "Deployed",
+          status: "True",
+          lastTransitionTime: "2023-10-31T14:13:27Z",
+          reason: "Successful",
+          message: "Successful",
+        },
+      ],
+      phase: "Running",
+      config: {
+        jetstream: {
+          url: "nats://isbsvc-default-js-svc.numaflow-system.svc:4222",
+          auth: {
+            basic: {
+              user: {
+                name: "isbsvc-default-js-client-auth",
+                key: "client-auth-user",
+              },
+              password: {
+                name: "isbsvc-default-js-client-auth",
+                key: "client-auth-password",
+              },
+            },
+          },
+          streamConfig:
+            "consumer:\n  ackwait: 60s\n  maxackpending: 25000\notbucket:\n  history: 1\n  maxbytes: 0\n  maxvaluesize: 0\n  replicas: 3\n  storage: 0\n  ttl: 3h\nprocbucket:\n  history: 1\n  maxbytes: 0\n  maxvaluesize: 0\n  replicas: 3\n  storage: 0\n  ttl: 72h\nstream:\n  duplicates: 60s\n  maxage: 72h\n  maxbytes: -1\n  maxmsgs: 100000\n  replicas: 3\n  retention: 0\n  storage: 0\n",
+        },
+      },
+      type: "jetstream",
+    },
+  },
+};
+
+describe("PipelineISBSummaryStatus", () => {
+  it("should render the ISB summary status", () => {
+    render(
+      <AppContext.Provider value={{ isbData: mockData }}>
+        <BrowserRouter>
+          <PipelineISBSummaryStatus isbData={mockData} />
+        </BrowserRouter>
+      </AppContext.Provider>
+    );
+    expect(screen.getByText("ISB SERVICES SUMMARY")).toBeInTheDocument();
+    expect(screen.getByText("default")).toBeInTheDocument();
+    expect(screen.getByText("3")).toBeInTheDocument();
+    expect(screen.getByText("jetstream")).toBeInTheDocument();
+  });
+
+  it("should render the ISB summary status with unknown values", () => {
+    render(
+      <AppContext.Provider value={{ isbData: mockData }}>
+        <BrowserRouter>
+          <PipelineISBSummaryStatus isbData={null} />
+        </BrowserRouter>
+      </AppContext.Provider>
+    );
+    expect(screen.getByText("ISB SERVICES SUMMARY")).toBeInTheDocument();
+  });
+});
diff --git a/ui/src/components/pages/Pipeline/partials/PipelineStatus/index.test.tsx b/ui/src/components/pages/Pipeline/partials/PipelineStatus/index.test.tsx
new file mode 100644
index 00000000..0ee616ab
--- /dev/null
+++ b/ui/src/components/pages/Pipeline/partials/PipelineStatus/index.test.tsx
@@ -0,0 +1,20 @@
+import React from "react";
+import { render, screen } from "@testing-library/react";
+import "@testing-library/jest-dom";
+import { AppContext } from "../../../../../App";
+import { BrowserRouter } from "react-router-dom";
+import { PipelineStatus } from "./index";
+
+describe("PipelineStatus", () => {
+  it("should render the component", () => {
+    render(
+      <AppContext.Provider value="healthy">
+        <BrowserRouter>
+          <PipelineStatus status="Running" healthStatus="healthy" />
+        </BrowserRouter>
+      </AppContext.Provider>
+    );
+
+    expect(screen.getByText("STATUS")).toBeInTheDocument();
+  });
+});
diff --git a/ui/src/components/pages/Pipeline/partials/PipelineSummaryStatus/index.test.tsx b/ui/src/components/pages/Pipeline/partials/PipelineSummaryStatus/index.test.tsx
new file mode 100644
index 00000000..b8d821a8
--- /dev/null
+++ b/ui/src/components/pages/Pipeline/partials/PipelineSummaryStatus/index.test.tsx
@@ -0,0 +1,229 @@
+import React from "react";
+import { render, screen, waitFor } from "@testing-library/react";
+import "@testing-library/jest-dom";
+import { AppContext } from "../../../../../App";
+import { BrowserRouter } from "react-router-dom";
+import { PipelineSummaryStatus } from "./index";
+import { act } from "react-dom/test-utils";
+
+const mockRefresh = jest.fn();
+const mockPipeline = {
+  kind: "Pipeline",
+  apiVersion: "numaflow.numaproj.io/v1alpha1",
+  metadata: {
+    name: "simple-pipeline",
+    namespace: "numaflow-system",
+    uid: "9a3fa74f-d51e-4e67-a842-b32f531aa8fd",
+    resourceVersion: "381794",
+    generation: 54,
+    creationTimestamp: "2023-10-31T14:13:34Z",
+    annotations: {
+      "kubectl.kubernetes.io/last-applied-configuration":
+        '{"apiVersion":"numaflow.numaproj.io/v1alpha1","kind":"Pipeline","metadata":{"annotations":{},"name":"simple-pipeline","namespace":"numaflow-system"},"spec":{"edges":[{"from":"in","to":"cat"},{"from":"cat","to":"out"}],"vertices":[{"name":"in","source":{"generator":{"duration":"1s","rpu":5}}},{"name":"cat","udf":{"builtin":{"name":"cat"}}},{"name":"out","sink":{"log":{}}}]}}\n',
+    },
+    finalizers: ["pipeline-controller"],
+    managedFields: [
+      {
+        manager: "kubectl-client-side-apply",
+        operation: "Update",
+        apiVersion: "numaflow.numaproj.io/v1alpha1",
+        time: "2023-10-31T14:13:34Z",
+        fieldsType: "FieldsV1",
+        fieldsV1: {
+          "f:metadata": {
+            "f:annotations": {
+              ".": {},
+              "f:kubectl.kubernetes.io/last-applied-configuration": {},
+            },
+          },
+          "f:spec": {
+            ".": {},
+            "f:edges": {},
+            "f:lifecycle": {
+              ".": {},
+              "f:deleteGracePeriodSeconds": {},
+              "f:pauseGracePeriodSeconds": {},
+            },
+            "f:limits": {
+              ".": {},
+              "f:bufferMaxLength": {},
+              "f:bufferUsageLimit": {},
+              "f:readBatchSize": {},
+              "f:readTimeout": {},
+            },
+            "f:watermark": {
+              ".": {},
+              "f:disabled": {},
+              "f:maxDelay": {},
+            },
+          },
+        },
+      },
+      {
+        manager: "numaflow",
+        operation: "Update",
+        apiVersion: "numaflow.numaproj.io/v1alpha1",
+        time: "2023-10-31T16:57:10Z",
+        fieldsType: "FieldsV1",
+        fieldsV1: {
+          "f:metadata": {
+            "f:finalizers": {
+              ".": {},
+              'v:"pipeline-controller"': {},
+            },
+          },
+          "f:spec": {
+            "f:lifecycle": {
+              "f:desiredPhase": {},
+            },
+            "f:vertices": {},
+          },
+        },
+      },
+      {
+        manager: "numaflow",
+        operation: "Update",
+        apiVersion: "numaflow.numaproj.io/v1alpha1",
+        time: "2023-10-31T16:57:19Z",
+        fieldsType: "FieldsV1",
+        fieldsV1: {
+          "f:status": {
+            ".": {},
+            "f:conditions": {},
+            "f:lastUpdated": {},
+            "f:phase": {},
+            "f:sinkCount": {},
+            "f:sourceCount": {},
+            "f:udfCount": {},
+            "f:vertexCount": {},
+          },
+        },
+        subresource: "status",
+      },
+    ],
+  },
+  spec: {
+    vertices: [
+      {
+        name: "in",
+        source: {
+          generator: {
+            rpu: 5,
+            duration: "1s",
+            msgSize: 8,
+          },
+        },
+        scale: {},
+      },
+      {
+        name: "cat",
+        udf: {
+          container: null,
+          builtin: {
+            name: "cat",
+          },
+          groupBy: null,
+        },
+        scale: {},
+      },
+      {
+        name: "out",
+        sink: {
+          log: {},
+        },
+        scale: {},
+      },
+    ],
+    edges: [
+      {
+        from: "in",
+        to: "cat",
+        conditions: null,
+      },
+      {
+        from: "cat",
+        to: "out",
+        conditions: null,
+      },
+    ],
+    lifecycle: {
+      deleteGracePeriodSeconds: 30,
+      desiredPhase: "Running",
+      pauseGracePeriodSeconds: 30,
+    },
+    limits: {
+      readBatchSize: 500,
+      bufferMaxLength: 30000,
+      bufferUsageLimit: 80,
+      readTimeout: "1s",
+    },
+    watermark: {
+      maxDelay: "0s",
+    },
+  },
+  status: {
+    conditions: [
+      {
+        type: "Configured",
+        status: "True",
+        lastTransitionTime: "2023-10-31T16:57:19Z",
+        reason: "Successful",
+        message: "Successful",
+      },
+      {
+        type: "Deployed",
+        status: "True",
+        lastTransitionTime: "2023-10-31T16:57:19Z",
+        reason: "Successful",
+        message: "Successful",
+      },
+    ],
+    phase: "Running",
+    lastUpdated: "2023-10-31T16:57:19Z",
+    vertexCount: 3,
+    sourceCount: 1,
+    sinkCount: 1,
+    udfCount: 1,
+  },
+};
+
+describe("PipelineSummaryStatus", () => {
+  it("should render the component", () => {
+    render(
+      <AppContext.Provider value="healthy">
+        <BrowserRouter>
+          <PipelineSummaryStatus
+            pipelineId="simple-pipeline"
+            pipeline={mockPipeline}
+            lag={0}
+            refresh={mockRefresh}
+          />
+        </BrowserRouter>
+      </AppContext.Provider>
+    );
+
+    expect(screen.getByText("SUMMARY")).toBeInTheDocument();
+  });
+
+  it("Should click on the spec button", async () => {
+    render(
+      <AppContext.Provider value="healthy">
+        <BrowserRouter>
+          <PipelineSummaryStatus
+            pipelineId="simple-pipeline"
+            pipeline={mockPipeline}
+            lag={0}
+            refresh={mockRefresh}
+          />
+        </BrowserRouter>
+      </AppContext.Provider>
+    );
+
+    await waitFor(() => {
+      expect(screen.getByTestId("pipeline-spec-click")).toBeInTheDocument();
+    });
+    await act(async () => {
+      screen.getByTestId("pipeline-spec-click").click();
+    });
+  });
+});