This is an automated email from the ASF dual-hosted git repository. zqr10159 pushed a commit to branch 2.0.0 in repository https://gitbox.apache.org/repos/asf/hertzbeat.git
commit 72ddbb80203eacec94cee63662773b5694ee23c4 Author: Logic <[email protected]> AuthorDate: Fri May 29 22:08:23 2026 +0800 fix(web-next): scope topology legend to visible graph types --- web-next/app/topology/page.test.tsx | 2 ++ web-next/app/topology/topology-page.tsx | 24 ++++++++++++++++++----- web-next/app/ui-lab/page.test.tsx | 5 +++++ web-next/app/ui-lab/page.tsx | 8 +++++++- web-next/packages/hertzbeat-ui/src/index.test.tsx | 23 ++++++++++++++++++++++ web-next/packages/hertzbeat-ui/src/index.tsx | 5 +++-- 6 files changed, 59 insertions(+), 8 deletions(-) diff --git a/web-next/app/topology/page.test.tsx b/web-next/app/topology/page.test.tsx index 5f5f377db7..33ffd36fbd 100644 --- a/web-next/app/topology/page.test.tsx +++ b/web-next/app/topology/page.test.tsx @@ -956,6 +956,8 @@ describe('topology page', () => { expect(html).not.toContain('data-hz-topology-legend-section="confidence"'); expect(html).toContain('data-hz-topology-legend-item="node-type-service"'); expect(html).toContain('data-hz-topology-legend-item="node-type-database"'); + expect(html).not.toContain('data-hz-topology-legend-item="node-type-alert"'); + expect(html).not.toContain('data-hz-topology-legend-item="node-type-resource"'); expect(html).toContain('data-hz-topology-legend-icon-library="lucide-react"'); expect(html).toContain('data-hz-topology-legend-icon-source="entity-type-catalog"'); expect(html).toContain('data-hz-topology-legend-icon-name="blocks"'); diff --git a/web-next/app/topology/topology-page.tsx b/web-next/app/topology/topology-page.tsx index f40c4c4d33..ea04862bef 100644 --- a/web-next/app/topology/topology-page.tsx +++ b/web-next/app/topology/topology-page.tsx @@ -45,6 +45,7 @@ import { HZ_TOPOLOGY_G6_NODE_ICON_CATALOG, buildHzTopologyG6LargeGraphStrategy, buildHzTopologyG6RenderWindow, + getHzTopologyG6NodeIcon, type HzTopologyG6GraphInput, type HzTopologyG6HoverAnchor } from '@hertzbeat/ui/topology-g6'; @@ -72,6 +73,13 @@ function findEdge(edges: TopologyServiceEdge[], id: string) { return edges.find(edge => edge.id === id); } +function resolveTopologyG6EntityType(node: Pick<TopologyServiceNode, 'entityType' | 'id' | 'label' | 'source'>) { + const directIcon = getHzTopologyG6NodeIcon(node.entityType); + if (directIcon.kind !== 'unknown') return directIcon.kind; + const inferredIcon = getHzTopologyG6NodeIcon(`${node.label} ${node.source} ${node.id}`); + return inferredIcon.kind === 'unknown' ? node.entityType : inferredIcon.kind; +} + function formatTimeRange(timeRange: string, t: (key: string) => string) { if (timeRange === 'last-1h') return t('topology.time.last-1h'); return timeRange; @@ -738,7 +746,7 @@ export default function TopologyPage({ nodes: topologyCanvasNodes.map(node => ({ id: node.id, label: node.label, - entityType: node.entityType, + entityType: resolveTopologyG6EntityType(node), health: node.health, tone: node.tone, focus: node.focus, @@ -1124,8 +1132,13 @@ export default function TopologyPage({ ? t('topology.degraded.api.source') : 'API'; const topologyLegendNodeTypeItems = React.useMemo( - () => - HZ_TOPOLOGY_G6_NODE_ICON_CATALOG.filter(icon => icon.kind !== 'unknown').map(icon => ({ + () => { + const visibleNodeKinds = new Set( + topologyG6Graph.nodes + .map(node => getHzTopologyG6NodeIcon(node.entityType).kind) + .filter(kind => kind !== 'unknown') + ); + return HZ_TOPOLOGY_G6_NODE_ICON_CATALOG.filter(icon => visibleNodeKinds.has(icon.kind)).map(icon => ({ id: `node-type-${icon.kind}`, label: t(`topology.legend.node-type.${icon.kind}`), value: icon.iconName, @@ -1135,8 +1148,9 @@ export default function TopologyPage({ iconName: icon.iconName, iconSource: icon.iconSource, visualSource: 'lucide-react' as const - })), - [t] + })); + }, + [topologyG6Graph.nodes, t] ); const topologyLegendSections = React.useMemo( () => [ diff --git a/web-next/app/ui-lab/page.test.tsx b/web-next/app/ui-lab/page.test.tsx index 6b600075e3..73be41f88f 100644 --- a/web-next/app/ui-lab/page.test.tsx +++ b/web-next/app/ui-lab/page.test.tsx @@ -2697,6 +2697,11 @@ describe('HertzBeat UI lab page', () => { expect(html).not.toContain('data-hz-topology-legend-section="confidence"'); expect(html).toContain('data-hz-topology-legend-item="node-type-service"'); expect(html).toContain('data-hz-topology-legend-item="node-type-database"'); + expect(html).toContain('data-hz-topology-legend-item="node-type-middleware"'); + expect(html).toContain('data-hz-topology-legend-item="node-type-monitor"'); + expect(html).not.toContain('data-hz-topology-legend-item="node-type-application"'); + expect(html).not.toContain('data-hz-topology-legend-item="node-type-alert"'); + expect(html).not.toContain('data-hz-topology-legend-item="node-type-resource"'); expect(html).toContain('data-hz-topology-legend-icon-library="lucide-react"'); expect(html).toContain('data-hz-topology-legend-icon-source="entity-type-catalog"'); expect(html).toContain('data-hz-topology-legend-icon-no-handdrawn="true"'); diff --git a/web-next/app/ui-lab/page.tsx b/web-next/app/ui-lab/page.tsx index 6d4aa16b02..0693307083 100644 --- a/web-next/app/ui-lab/page.tsx +++ b/web-next/app/ui-lab/page.tsx @@ -209,6 +209,7 @@ import { buildHzTopologyG6RenderWindow, buildHzTopologyG6ScaleFixture, buildHzTopologyG6ScaleProfile, + getHzTopologyG6NodeIcon, type HzTopologyG6GraphInput } from '@hertzbeat/ui/topology-g6'; import { AlertNoticeRuleSwitch } from '../../components/pages/alert-notice-rule-fields'; @@ -495,7 +496,12 @@ const topologyG6LargeGraphStrategy500 = buildHzTopologyG6LargeGraphStrategy(topo const topologyG6ScaleLabRenderWindow500 = buildHzTopologyG6RenderWindow(topologyG6ScaleLabGraph500, topologyG6LargeGraphStrategy500, { priorityNodeIds: ['scale-svc-420'] }); -const topologyG6NodeTypeLegendItems = HZ_TOPOLOGY_G6_NODE_ICON_CATALOG.filter(icon => icon.kind !== 'unknown').map(icon => ({ +const topologyG6LabVisibleNodeKinds = new Set( + topologyG6LabGraph.nodes.map(node => getHzTopologyG6NodeIcon(node.entityType).kind).filter(kind => kind !== 'unknown') +); +const topologyG6NodeTypeLegendItems = HZ_TOPOLOGY_G6_NODE_ICON_CATALOG.filter(icon => + topologyG6LabVisibleNodeKinds.has(icon.kind) +).map(icon => ({ id: `node-type-${icon.kind}`, label: icon.label, value: icon.iconName, diff --git a/web-next/packages/hertzbeat-ui/src/index.test.tsx b/web-next/packages/hertzbeat-ui/src/index.test.tsx index e045fa1fc7..fa75a423af 100644 --- a/web-next/packages/hertzbeat-ui/src/index.test.tsx +++ b/web-next/packages/hertzbeat-ui/src/index.test.tsx @@ -5262,6 +5262,29 @@ describe('@hertzbeat/ui', () => { expect(html).not.toContain('2 groups'); }); + it('omits empty topology legend sections so graph-scoped legends do not advertise absent node types', () => { + const html = renderToStaticMarkup( + <HzTopologyLegend + title="Legend" + boundary="flush" + density="canvas-dock" + sections={[ + { id: 'node-type', label: 'Node type', items: [] }, + { + id: 'status', + label: 'Status', + items: [{ id: 'healthy-node', label: 'Healthy', value: 'healthy', visualSource: 'hertzbeat-status-token' }] + } + ]} + /> + ); + + expect(html).not.toContain('data-hz-topology-legend-section="node-type"'); + expect(html).not.toContain('Node type'); + expect(html).toContain('data-hz-topology-legend-section="status"'); + expect(html).toContain('data-hz-topology-legend-item="healthy-node"'); + }); + it('renders a topology detail drawer for edge evidence and cross-signal handoffs', () => { const html = renderToStaticMarkup( <HzTopologyDetailDrawer diff --git a/web-next/packages/hertzbeat-ui/src/index.tsx b/web-next/packages/hertzbeat-ui/src/index.tsx index f9cfcc8d04..9d2cf77344 100644 --- a/web-next/packages/hertzbeat-ui/src/index.tsx +++ b/web-next/packages/hertzbeat-ui/src/index.tsx @@ -11204,6 +11204,7 @@ export function HzTopologyLegend({ ...props }: HzTopologyLegendProps) { const isCanvasDock = density === 'canvas-dock'; + const visibleSections = sections.filter(section => section.items.length > 0); return ( <section @@ -11247,7 +11248,7 @@ export function HzTopologyLegend({ className="font-mono text-[10px] uppercase tracking-[0.08em] text-[#727b8c]" data-hz-topology-legend-summary-owner="hertzbeat-ui-legend-summary" > - {summaryLabel ?? `${sections.length} groups`} + {summaryLabel ?? `${visibleSections.length} groups`} </span> ) : null} </header> @@ -11257,7 +11258,7 @@ export function HzTopologyLegend({ isCanvasDock ? 'flex flex-wrap gap-x-3 gap-y-1 px-2 py-1.5' : 'grid divide-y divide-[var(--hz-ui-line-faint)]' )} > - {sections.map(section => ( + {visibleSections.map(section => ( <div key={section.id} className={cn( --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
