This is an automated email from the ASF dual-hosted git repository.

wu-sheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git


The following commit(s) were added to refs/heads/main by this push:
     new bbcfe31  ui: network-profiling topology — drop namespace grouping, 
auto-fit pod hexagon
bbcfe31 is described below

commit bbcfe3180e8acd27790cfd59edaa9f020465647a
Author: Wu Sheng <[email protected]>
AuthorDate: Wed May 20 10:47:17 2026 +0800

    ui: network-profiling topology — drop namespace grouping, auto-fit pod 
hexagon
    
    Per design feedback:
      - Removed the namespace group concept (tinted group regions, legend,
        per-namespace node tinting + labels) and its config plumbing
        (ProcessTopologyConfig.groupExpression, LayerDef.processTopology,
        the admin group-expression field). The edge-metric config
        (edgeClient/edgeServer) stays — only grouping is gone.
      - Inside cells are accent-tinted, external cells grey (uniform again).
      - The dashed pod-boundary hexagon now auto-fits the inside processes
        as tightly as possible: centre = inside-cell centroid, circumradius
        = max cell distance × 1.16 (the flat-top inradius factor) so cells
        near a flat edge still fit. Outside peer rings start past the fitted
        boundary instead of a fixed radius.
      - The boundary is recomputed live while an inside node is dragged
        (redrawBoundary on each drag tick) so it keeps wrapping the cluster.
    
    Node popover + animated directional edges + edge dashboard modal are
    unchanged.
---
 apps/bff/src/http/query/menu.ts                    |   3 -
 .../admin/layer-templates/LayerDashboardsAdmin.vue |  22 ---
 .../layer/profiling/LayerNetworkProfilingView.vue  |   7 -
 .../src/layer/profiling/ProcessTopologyGraph.vue   | 204 +++++++--------------
 packages/api-client/src/menu.ts                    |   5 -
 packages/api-client/src/topology.ts                |   6 -
 6 files changed, 66 insertions(+), 181 deletions(-)

diff --git a/apps/bff/src/http/query/menu.ts b/apps/bff/src/http/query/menu.ts
index d9db665..2eae4d0 100644
--- a/apps/bff/src/http/query/menu.ts
+++ b/apps/bff/src/http/query/menu.ts
@@ -182,9 +182,6 @@ function deriveLayer(
       overview: tpl.overview,
       log: tpl.log,
       traces: tpl.traces,
-      processTopology: tpl.processTopology
-        ? { groupExpression: tpl.processTopology.groupExpression }
-        : undefined,
       naming: tpl.naming,
     };
   }
diff --git 
a/apps/ui/src/features/admin/layer-templates/LayerDashboardsAdmin.vue 
b/apps/ui/src/features/admin/layer-templates/LayerDashboardsAdmin.vue
index a59973d..874842c 100644
--- a/apps/ui/src/features/admin/layer-templates/LayerDashboardsAdmin.vue
+++ b/apps/ui/src/features/admin/layer-templates/LayerDashboardsAdmin.vue
@@ -636,16 +636,6 @@ const processEdgeClientMetrics = computed(() =>
 const processEdgeServerMetrics = computed(() =>
   activeScope.value === 'networkProfiling' ? getMetricList('edgeServer') : [],
 );
-const processGroupExpression = computed<string>({
-  get: () =>
-    activeScope.value === 'networkProfiling'
-      ? (draft.template?.processTopology?.groupExpression ?? '')
-      : '',
-  set: (v: string) => {
-    const t = ensureProcessTopology();
-    t.groupExpression = v.trim() || undefined;
-  },
-});
 
 /* Trace backend selector. `traces.source` decides which trace store the
  * per-layer Trace tab dispatches to: `native` (SkyWalking query-protocol),
@@ -1589,18 +1579,6 @@ const namingTest = computed<NamingTestResult>(() => {
             <h4>Network profiling — process-relation config</h4>
             <span class="sub">edge MQE for the process-topology detail panel. 
Queried under ProcessRelation when an operator clicks a process→process 
call.</span>
           </div>
-          <div class="naming-prefix-row">
-            <label class="mf mf-wide">
-              <span>Group expression</span>
-              <input
-                v-model="processGroupExpression"
-                type="text"
-                class="mf-input mono"
-                placeholder="(optional regex — 1st capture = namespace; 
default: text after last '.')"
-              />
-            </label>
-            <span class="naming-prefix-hint">Groups the topology honeycomb by 
namespace (k8s <code>name.namespace</code> convention).</span>
-          </div>
           <div class="topo-cfg-body">
             <div class="topo-cfg-section">
               <header class="topo-cfg-head">
diff --git a/apps/ui/src/layer/profiling/LayerNetworkProfilingView.vue 
b/apps/ui/src/layer/profiling/LayerNetworkProfilingView.vue
index 44a7f12..f47cdc9 100644
--- a/apps/ui/src/layer/profiling/LayerNetworkProfilingView.vue
+++ b/apps/ui/src/layer/profiling/LayerNetworkProfilingView.vue
@@ -35,11 +35,9 @@ import { computed, ref, watch } from 'vue';
 import { useRoute } from 'vue-router';
 import { useLayerInstances } from '@/layer/useLayerInstances';
 import { useSelectedService } from '@/layer/useSelectedService';
-import { useLayers } from '@/shell/useLayers';
 import { bffClient } from '@/api/client';
 import type {
   EBPFTask,
-  LayerDef,
   NetworkProfilingSampling,
   ProcessCall,
   ProcessNode,
@@ -53,10 +51,6 @@ import Icon from '@/components/icons/Icon.vue';
 const route = useRoute();
 const layerKey = computed(() => String(route.params.layerKey ?? ''));
 const { selectedId: serviceId } = useSelectedService();
-const { layers } = useLayers();
-const layer = computed<LayerDef | null>(
-  () => layers.value.find((l) => l.key === layerKey.value) ?? null,
-);
 
 // Instance picker (binds to ?serviceInstance= via plain ref state — the
 // network view needs an *instance* to be useful, so we don't reuse the
@@ -375,7 +369,6 @@ function fmtTime(ms: number): string {
           v-if="nodes.length"
           :nodes="nodes"
           :calls="calls"
-          :group-expression="layer?.processTopology?.groupExpression"
           @select-call="onSelectCall"
         />
         <div v-else-if="!topologyLoading" class="topology-empty">
diff --git a/apps/ui/src/layer/profiling/ProcessTopologyGraph.vue 
b/apps/ui/src/layer/profiling/ProcessTopologyGraph.vue
index cdb0213..2e8e22a 100644
--- a/apps/ui/src/layer/profiling/ProcessTopologyGraph.vue
+++ b/apps/ui/src/layer/profiling/ProcessTopologyGraph.vue
@@ -15,18 +15,14 @@
   limitations under the License.
 -->
 <!--
-  Process-level topology for network profiling — honeycomb + namespace
-  grouping, the same vocabulary as the k8s service topology.
+  Process-level topology for network profiling.
 
   - Inside-pod processes (`isReal` / synthetic `UNKNOWN_LOCAL`) and
-    external peers are each rendered as a flat-top hexagon CELL packed
-    in a honeycomb (inside) or on rings (outside) within / around the
-    pod-boundary hexagon.
-  - Nodes are grouped by NAMESPACE: by default the text after the last
-    `.` in the name (`demo-oap-xxx.skywalking-showcase` → `skywalking-
-    showcase`); an optional `groupExpression` regex overrides it (first
-    capture group wins). Each namespace gets a tinted region backdrop +
-    label + a colour in the legend.
+    external peers are each rendered as a flat-top hexagon CELL. Inside
+    cells pack a honeycomb around the centre; external peers ring it.
+  - A dashed hexagon boundary wraps the inside processes as tightly as
+    possible (auto-fit to the inside cells) and is recomputed live while
+    a node is dragged.
   - Edges are directed, animated (dashes flow source→target to show
     traffic direction) and clickable.
   - Clicking a node shows a floating info popover; clicking an edge
@@ -41,7 +37,7 @@ import * as d3 from 'd3';
 import type { ProcessCall, ProcessNode } from '@/api/client';
 
 interface Pt { x: number; y: number }
-type PositionedNode = ProcessNode & Pt & { ns: string };
+type PositionedNode = ProcessNode & Pt;
 interface PositionedCall {
   id: string;
   source: PositionedNode;
@@ -50,43 +46,14 @@ interface PositionedCall {
   lowerArc: boolean;
 }
 
-const props = defineProps<{
-  nodes: ProcessNode[];
-  calls: ProcessCall[];
-  groupExpression?: string;
-}>();
+const props = defineProps<{ nodes: ProcessNode[]; calls: ProcessCall[] }>();
 const emit = defineEmits<{ (e: 'select-call', c: ProcessCall | null): void 
}>();
 
 const host = ref<HTMLDivElement | null>(null);
 const selectedCallId = ref<string | null>(null);
 
-// ── Namespace grouping ──────────────────────────────────────────────
-const NS_LOCAL = '·local';
-function namespaceOf(name: string): string {
-  const expr = props.groupExpression?.trim();
-  if (expr) {
-    try {
-      const m = new RegExp(expr).exec(name);
-      if (m) return m[1] || m[0] || NS_LOCAL;
-    } catch {
-      /* invalid regex — fall through to the dot heuristic */
-    }
-  }
-  const i = name.lastIndexOf('.');
-  return i > 0 ? name.slice(i + 1) : NS_LOCAL;
-}
-function hueOf(ns: string): number {
-  let h = 0;
-  for (let i = 0; i < ns.length; i++) h = (h * 31 + ns.charCodeAt(i)) | 0;
-  return Math.abs(h) % 360;
-}
-function groupColor(ns: string): string {
-  return ns === NS_LOCAL ? 'var(--sw-accent, #f97316)' : `hsl(${hueOf(ns)}, 
55%, 56%)`;
-}
-
 // ── Hex geometry (flat-top) ─────────────────────────────────────────
 const SQRT3 = Math.sqrt(3);
-const HEX_RADIUS = 210;
 function axialToPixel(ax: number, ay: number, r: number, o: Pt): Pt {
   return { x: (1.5 * ax) * r + o.x, y: ((SQRT3 / 2) * ax + SQRT3 * ay) * r + 
o.y };
 }
@@ -124,10 +91,7 @@ function hexCellPath(cx: number, cy: number, R: number): 
string {
     const a = Math.PI * 2 * (i / 6);
     v.push([cx + Math.cos(a) * R, cy + Math.sin(a) * R]);
   }
-  return (d3.line().curve(d3.curveLinearClosed)(v) ?? '');
-}
-function hexBoundaryPath(r: number, o: Pt): string {
-  return hexCellPath(o.x, o.y, r);
+  return d3.line().curve(d3.curveLinearClosed)(v) ?? '';
 }
 
 function isInside(n: ProcessNode): boolean {
@@ -141,23 +105,17 @@ function protocolOf(c: ProcessCall): string {
   return 'TCP';
 }
 
-// Live legend entries (namespace → colour) for the overlay.
-const legend = ref<Array<{ ns: string; color: string; count: number }>>([]);
 let cellRadius = 26;
+let positioned: PositionedNode[] = [];
 
 function layout(o: Pt): PositionedNode[] {
-  const withNs = (n: ProcessNode): PositionedNode =>
-    ({ ...n, ns: namespaceOf(n.name) }) as PositionedNode;
-  // Sort by namespace so the spiral / ring fill keeps a group contiguous.
-  const byNs = (a: PositionedNode, b: PositionedNode) => 
a.ns.localeCompare(b.ns) || a.name.localeCompare(b.name);
-
-  const inside = props.nodes.filter(isInside).map(withNs).sort(byNs);
-  const outside = props.nodes.filter((n) => 
!isInside(n)).map(withNs).sort(byNs);
-
-  // Inside honeycomb. Pick a cell size that fits the spiral inside the
-  // pod boundary: the outermost ring index ~ sqrt(count).
-  const rings = Math.max(1, Math.ceil((-3 + Math.sqrt(9 + 12 * (inside.length 
- 1))) / 6));
-  cellRadius = Math.max(14, Math.min(34, (HEX_RADIUS * 0.82) / ((rings + 0.6) 
* SQRT3)));
+  const inside = props.nodes.filter(isInside).map((n) => ({ ...n }) as 
PositionedNode);
+  const outside = props.nodes.filter((n) => !isInside(n)).map((n) => ({ ...n 
}) as PositionedNode);
+
+  // Inside honeycomb. Cell size scales down as the spiral grows so the
+  // packed cluster stays compact.
+  const rings = Math.max(1, Math.ceil((-3 + Math.sqrt(9 + 12 * Math.max(1, 
inside.length - 1))) / 6));
+  cellRadius = Math.max(16, Math.min(34, 150 / (rings + 0.6)));
   const cells = spiralHex(inside.length);
   inside.forEach((n, i) => {
     const c = cells[i] ?? { x: 0, y: 0 };
@@ -166,8 +124,9 @@ function layout(o: Pt): PositionedNode[] {
     n.y = p.y;
   });
 
-  // Outside peers on expanding rings.
-  let r = HEX_RADIUS + 60;
+  // Outside peers ring the (tight) inside cluster.
+  const startR = insideExtent(inside, o) + 90;
+  let r = startR;
   let ring = circlePoints(r, 26, o);
   outside.forEach((n, i) => {
     if (!ring[i]) {
@@ -179,16 +138,30 @@ function layout(o: Pt): PositionedNode[] {
     n.y = p.y;
   });
 
-  // Build legend (sorted, local group last).
-  const counts = new Map<string, number>();
-  for (const n of [...inside, ...outside]) counts.set(n.ns, (counts.get(n.ns) 
?? 0) + 1);
-  legend.value = [...counts.entries()]
-    .sort((a, b) => (a[0] === NS_LOCAL ? 1 : b[0] === NS_LOCAL ? -1 : 
a[0].localeCompare(b[0])))
-    .map(([ns, count]) => ({ ns, color: groupColor(ns), count }));
-
   return [...inside, ...outside];
 }
 
+/** Max distance from `o` to any inside cell centre. */
+function insideExtent(inside: PositionedNode[], o: Pt): number {
+  let r = 0;
+  for (const n of inside) r = Math.max(r, Math.hypot(n.x - o.x, n.y - o.y));
+  return r;
+}
+
+/** Smallest flat-top hexagon (centre + circumradius) that wraps the
+ *  inside cells. Circumradius is inflated past the cell extent because a
+ *  flat-top hexagon's inradius (along its flat edges) is only √3/2 of
+ *  the circumradius — so cells sitting near a flat edge still fit. */
+function insideBoundary(): { cx: number; cy: number; r: number } {
+  const inside = positioned.filter(isInside);
+  if (inside.length === 0) return { cx: 0, cy: 0, r: cellRadius * 1.4 };
+  const cx = d3.mean(inside, (n) => n.x) ?? 0;
+  const cy = d3.mean(inside, (n) => n.y) ?? 0;
+  let maxD = 0;
+  for (const n of inside) maxD = Math.max(maxD, Math.hypot(n.x - cx, n.y - 
cy));
+  return { cx, cy, r: (maxD + cellRadius) * 1.16 + 4 };
+}
+
 function buildCalls(byId: Map<string, PositionedNode>): PositionedCall[] {
   const seen = new Set<string>();
   const out: PositionedCall[] = [];
@@ -244,6 +217,8 @@ const popStyle = computed(() => {
 let edgeSel: d3.Selection<SVGPathElement, PositionedCall, SVGGElement, 
unknown> | null = null;
 let pillSel: d3.Selection<SVGGElement, PositionedCall, SVGGElement, unknown> | 
null = null;
 let nodeSel: d3.Selection<SVGGElement, PositionedNode, SVGGElement, unknown> | 
null = null;
+let boundarySel: d3.Selection<SVGPathElement, unknown, null, undefined> | null 
= null;
+let boundaryLabelSel: d3.Selection<SVGTextElement, unknown, null, undefined> | 
null = null;
 
 function instanceLabel(): string {
   return props.nodes.find(isInside)?.serviceInstanceName ?? '';
@@ -255,6 +230,11 @@ function restyleEdges(): void {
     )
     .attr('stroke-width', (d) => (d.id === selectedCallId.value ? 2.6 : 1.5));
 }
+function redrawBoundary(): void {
+  const b = insideBoundary();
+  boundarySel?.attr('d', hexCellPath(b.cx, b.cy, b.r));
+  boundaryLabelSel?.attr('x', b.cx).attr('y', b.cy + b.r + 16);
+}
 function refreshPositions(): void {
   edgeSel?.attr('d', edgePath);
   pillSel?.attr('transform', (d) => {
@@ -273,7 +253,7 @@ function render(): void {
   const h = rect.height || 520;
   const o: Pt = { x: 0, y: 0 };
 
-  const positioned = layout(o);
+  positioned = layout(o);
   const byId = new Map(positioned.map((n) => [n.id, n]));
   const calls = buildCalls(byId);
 
@@ -307,55 +287,26 @@ function render(): void {
     .attr('d', 'M0,-5 L10,0 L0,5')
     .attr('fill', 'var(--sw-fg-3, #6c7080)');
 
-  // Pod boundary.
-  g.append('path')
-    .attr('d', hexBoundaryPath(HEX_RADIUS, o))
+  // Pod boundary — auto-fit to the inside cells, redrawn on drag.
+  const b0 = insideBoundary();
+  boundarySel = g
+    .append('path')
+    .attr('d', hexCellPath(b0.cx, b0.cy, b0.r))
     .attr('fill', 'var(--sw-bg-1, #15171c)')
     .attr('fill-opacity', 0.3)
     .attr('stroke', 'var(--sw-line, #2a2d36)')
     .attr('stroke-dasharray', '4 4')
     .attr('stroke-width', 1.5);
-  g.append('text')
-    .attr('x', o.x)
-    .attr('y', o.y + HEX_RADIUS + 18)
+  boundaryLabelSel = g
+    .append('text')
+    .attr('x', b0.cx)
+    .attr('y', b0.cy + b0.r + 16)
     .attr('text-anchor', 'middle')
     .attr('fill', 'var(--sw-fg-2, #b4b7c2)')
     .style('font-family', 'var(--sw-mono, monospace)')
     .style('font-size', '11px')
     .text(instanceLabel());
 
-  // Namespace group regions (bounding circle behind each group's cells)
-  // + label, drawn beneath nodes — the k8s-topology grouping idiom.
-  const groups = d3.group(positioned, (n) => n.ns);
-  const groupG = g.append('g').attr('class', 'groups');
-  for (const [ns, members] of groups) {
-    if (ns === NS_LOCAL) continue; // pod-local processes already sit in the 
boundary
-    const cx = d3.mean(members, (m) => m.x) ?? 0;
-    const cy = d3.mean(members, (m) => m.y) ?? 0;
-    const rad = Math.max(...members.map((m) => Math.hypot(m.x - cx, m.y - 
cy))) + cellRadius + 8;
-    groupG
-      .append('circle')
-      .attr('cx', cx)
-      .attr('cy', cy)
-      .attr('r', rad)
-      .attr('fill', groupColor(ns))
-      .attr('fill-opacity', 0.07)
-      .attr('stroke', groupColor(ns))
-      .attr('stroke-opacity', 0.35)
-      .attr('stroke-dasharray', '3 3')
-      .attr('stroke-width', 1);
-    groupG
-      .append('text')
-      .attr('x', cx)
-      .attr('y', cy - rad - 4)
-      .attr('text-anchor', 'middle')
-      .attr('fill', groupColor(ns))
-      .style('font-family', 'var(--sw-mono, monospace)')
-      .style('font-size', '10px')
-      .style('font-weight', '600')
-      .text(ns);
-  }
-
   // Edges (animated flow).
   const linkG = g.append('g').attr('class', 'links');
   edgeSel = linkG
@@ -407,7 +358,7 @@ function render(): void {
     .style('font-size', '9px')
     .text((d) => d.protocol);
 
-  // Nodes — hex cells tinted by namespace.
+  // Nodes — hex cells (inside = accent, external = grey).
   nodeSel = g
     .append('g')
     .attr('class', 'nodes')
@@ -426,18 +377,21 @@ function render(): void {
     .call(
       d3
         .drag<SVGGElement, PositionedNode>()
-        .on('start', () => { nodePop.node = null; })
+        .on('start', () => {
+          nodePop.node = null;
+        })
         .on('drag', (ev, d) => {
           d.x = ev.x;
           d.y = ev.y;
           refreshPositions();
+          if (isInside(d)) redrawBoundary();
         }) as never,
     );
   nodeSel
     .append('path')
     .attr('d', (d) => hexCellPath(0, 0, isInside(d) ? cellRadius * 0.92 : 18))
-    .attr('fill', (d) => groupColor(d.ns))
-    .attr('fill-opacity', (d) => (isInside(d) ? 0.85 : 0.65))
+    .attr('fill', (d) => (isInside(d) ? 'var(--sw-accent, #f97316)' : 
'var(--sw-bg-3, #2a2d36)'))
+    .attr('fill-opacity', (d) => (isInside(d) ? 0.85 : 0.75))
     .attr('stroke', 'var(--sw-bg-0, #0d0f14)')
     .attr('stroke-width', 1.5);
   nodeSel
@@ -458,7 +412,7 @@ function render(): void {
 }
 
 onMounted(render);
-watch(() => [props.nodes, props.calls, props.groupExpression], render);
+watch(() => [props.nodes, props.calls], render);
 onBeforeUnmount(() => {
   if (host.value) host.value.innerHTML = '';
 });
@@ -467,21 +421,12 @@ onBeforeUnmount(() => {
 <template>
   <div class="topo-wrap">
     <div ref="host" class="topo-host"></div>
-    <!-- Namespace legend -->
-    <div v-if="legend.length" class="topo-legend">
-      <div v-for="l in legend" :key="l.ns" class="lg-row">
-        <span class="lg-swatch" :style="{ background: l.color }"></span>
-        <span class="lg-ns">{{ l.ns }}</span>
-        <span class="lg-count">{{ l.count }}</span>
-      </div>
-    </div>
     <!-- Node info floating popover -->
     <Teleport to="body">
       <div v-if="nodePop.node" class="topo-nodepop" :style="popStyle" 
role="tooltip">
         <div class="np-name">{{ nodePop.node.name }}</div>
         <dl class="np-rows">
           <div class="np-row"><dt>Kind</dt><dd>{{ nodePop.node.isReal ? 
'process' : 'virtual peer' }}</dd></div>
-          <div class="np-row"><dt>Namespace</dt><dd>{{ nodePop.node.ns 
}}</dd></div>
           <div class="np-row"><dt>Service</dt><dd>{{ nodePop.node.serviceName 
}}</dd></div>
           <div class="np-row"><dt>Instance</dt><dd>{{ 
nodePop.node.serviceInstanceName }}</dd></div>
         </dl>
@@ -507,23 +452,6 @@ onBeforeUnmount(() => {
 @keyframes topo-flow {
   to { stroke-dashoffset: -11; }
 }
-.topo-legend {
-  position: absolute;
-  top: 8px;
-  right: 8px;
-  max-height: calc(100% - 16px);
-  overflow-y: auto;
-  background: color-mix(in srgb, var(--sw-bg-1) 88%, transparent);
-  border: 1px solid var(--sw-line);
-  border-radius: 6px;
-  padding: 6px 8px;
-  font-size: 10.5px;
-  pointer-events: none;
-}
-.lg-row { display: grid; grid-template-columns: 12px 1fr auto; gap: 6px; 
align-items: center; padding: 1px 0; }
-.lg-swatch { width: 10px; height: 10px; border-radius: 2px; }
-.lg-ns { color: var(--sw-fg-1); font-family: var(--sw-mono); }
-.lg-count { color: var(--sw-fg-3); font-variant-numeric: tabular-nums; }
 </style>
 
 <style>
diff --git a/packages/api-client/src/menu.ts b/packages/api-client/src/menu.ts
index 75d32b0..97759dc 100644
--- a/packages/api-client/src/menu.ts
+++ b/packages/api-client/src/menu.ts
@@ -258,11 +258,6 @@ export interface LayerDef {
    *  spans (Envoy ALS, rover) so they set `source: 'zipkin'`; agent-
    *  traced layers default to `native`. */
   traces?: { source?: 'native' | 'zipkin' | 'both' };
-  /** Network-profiling process-topology hints the UI needs client-side.
-   *  Only `groupExpression` rides along (the MQE metric lists are
-   *  resolved server-side by the BFF). Drives the namespace grouping in
-   *  the process-topology honeycomb. */
-  processTopology?: { groupExpression?: string };
   /** Per-layer service-name parsing rule. When present, the UI runs
    *  every service name through this regex to derive `{ display, cluster }`
    *  and clusters topology nodes by cluster. Absent ⇒ no clustering. */
diff --git a/packages/api-client/src/topology.ts 
b/packages/api-client/src/topology.ts
index 4c6e969..4cfd970 100644
--- a/packages/api-client/src/topology.ts
+++ b/packages/api-client/src/topology.ts
@@ -119,12 +119,6 @@ export interface ProcessTopologyConfig {
   /** Per-edge MQE under ProcessRelation, server side
    *  (`process_relation_server_*`). */
   edgeServerMetrics: TopologyMetricDef[];
-  /** Optional regex used to derive the namespace each process node is
-   *  grouped under in the topology honeycomb. The first capture group
-   *  wins; when absent (or no match) the renderer falls back to the
-   *  text after the last `.` in the process name (the k8s
-   *  `name.namespace` convention). */
-  groupExpression?: string;
 }
 
 /** One resolved process-relation metric series for the edge panel. */

Reply via email to