Split dashboard selection policy owners

This commit is contained in:
rcourtman
2026-03-22 10:23:33 +00:00
parent bd4b6c07ed
commit b613a629f6
9 changed files with 164 additions and 49 deletions
@@ -70,39 +70,41 @@ regression protection.
48. `frontend-modern/src/components/Dashboard/useGroupedTableWindowing.ts`
49. `frontend-modern/src/components/Dashboard/workloadSelectors.ts`
50. `frontend-modern/src/components/Dashboard/workloadTopology.ts`
51. `frontend-modern/src/components/Dashboard/dashboardWorkloadRouteModel.ts`
52. `frontend-modern/src/components/Dashboard/dashboardWorkloadFilterConfigModel.ts`
53. `frontend-modern/src/components/Dashboard/dashboardWorkloadRouteStateModel.ts`
54. `frontend-modern/src/components/Dashboard/dashboardWorkloadUrlSyncModel.ts`
55. `frontend-modern/src/components/Dashboard/useDashboardWorkloadFilterOptions.ts`
56. `frontend-modern/src/components/Dashboard/__tests__/dashboardWorkloadFilterConfigModel.test.ts`
57. `frontend-modern/src/components/Dashboard/__tests__/dashboardWorkloadRouteModel.test.ts`
58. `frontend-modern/src/components/Dashboard/__tests__/dashboardWorkloadRouteStateModel.test.ts`
59. `frontend-modern/src/components/Dashboard/__tests__/dashboardWorkloadUrlSyncModel.test.ts`
60. `frontend-modern/src/components/Dashboard/__tests__/workloadTopology.test.ts`
61. `frontend-modern/src/components/Infrastructure/UnifiedResourceTable.tsx`
62. `frontend-modern/src/components/Infrastructure/useUnifiedResourceTableState.ts`
63. `frontend-modern/src/components/Infrastructure/infrastructureSelectors.ts`
64. `frontend-modern/src/components/Infrastructure/resourceDetailMappers.ts`
65. `frontend-modern/src/components/Dashboard/__tests__/Dashboard.performance.contract.test.tsx`
66. `frontend-modern/src/components/Dashboard/__tests__/DashboardFilter.test.tsx`
67. `frontend-modern/src/components/Dashboard/__tests__/useDashboardFilterState.test.ts`
68. `frontend-modern/src/components/Dashboard/__tests__/useDashboardSelectionState.test.ts`
69. `frontend-modern/src/components/Dashboard/MetricBar.test.tsx`
70. `frontend-modern/src/components/Dashboard/__tests__/useMetricBarState.test.tsx`
71. `frontend-modern/src/components/Dashboard/__tests__/EnhancedCPUBar.test.tsx`
72. `frontend-modern/src/components/Dashboard/__tests__/useEnhancedCPUBarState.test.tsx`
73. `frontend-modern/src/components/Dashboard/ThresholdSlider.test.tsx`
74. `frontend-modern/src/components/Dashboard/__tests__/useThresholdSliderState.test.ts`
75. `frontend-modern/src/components/Dashboard/__tests__/StackedDiskBar.test.tsx`
76. `frontend-modern/src/components/Dashboard/__tests__/useStackedDiskBarState.test.tsx`
77. `frontend-modern/src/components/Dashboard/StackedMemoryBar.test.tsx`
78. `frontend-modern/src/components/Dashboard/__tests__/useStackedMemoryBarState.test.tsx`
79. `frontend-modern/src/components/Dashboard/__tests__/DiskList.test.tsx`
80. `frontend-modern/src/components/Dashboard/__tests__/GuestRow.test.tsx`
81. `frontend-modern/src/components/Dashboard/GuestDrawer.test.tsx`
82. `frontend-modern/src/components/Dashboard/__tests__/useGroupedTableWindowing.test.ts`
83. `frontend-modern/src/components/Infrastructure/__tests__/UnifiedResourceTable.performance.contract.test.tsx`
51. `frontend-modern/src/components/Dashboard/dashboardSelectionModel.ts`
52. `frontend-modern/src/components/Dashboard/dashboardWorkloadRouteModel.ts`
53. `frontend-modern/src/components/Dashboard/dashboardWorkloadFilterConfigModel.ts`
54. `frontend-modern/src/components/Dashboard/dashboardWorkloadRouteStateModel.ts`
55. `frontend-modern/src/components/Dashboard/dashboardWorkloadUrlSyncModel.ts`
56. `frontend-modern/src/components/Dashboard/useDashboardWorkloadFilterOptions.ts`
57. `frontend-modern/src/components/Dashboard/__tests__/dashboardSelectionModel.test.ts`
58. `frontend-modern/src/components/Dashboard/__tests__/dashboardWorkloadFilterConfigModel.test.ts`
59. `frontend-modern/src/components/Dashboard/__tests__/dashboardWorkloadRouteModel.test.ts`
60. `frontend-modern/src/components/Dashboard/__tests__/dashboardWorkloadRouteStateModel.test.ts`
61. `frontend-modern/src/components/Dashboard/__tests__/dashboardWorkloadUrlSyncModel.test.ts`
62. `frontend-modern/src/components/Dashboard/__tests__/workloadTopology.test.ts`
63. `frontend-modern/src/components/Infrastructure/UnifiedResourceTable.tsx`
64. `frontend-modern/src/components/Infrastructure/useUnifiedResourceTableState.ts`
65. `frontend-modern/src/components/Infrastructure/infrastructureSelectors.ts`
66. `frontend-modern/src/components/Infrastructure/resourceDetailMappers.ts`
67. `frontend-modern/src/components/Dashboard/__tests__/Dashboard.performance.contract.test.tsx`
68. `frontend-modern/src/components/Dashboard/__tests__/DashboardFilter.test.tsx`
69. `frontend-modern/src/components/Dashboard/__tests__/useDashboardFilterState.test.ts`
70. `frontend-modern/src/components/Dashboard/__tests__/useDashboardSelectionState.test.ts`
71. `frontend-modern/src/components/Dashboard/MetricBar.test.tsx`
72. `frontend-modern/src/components/Dashboard/__tests__/useMetricBarState.test.tsx`
73. `frontend-modern/src/components/Dashboard/__tests__/EnhancedCPUBar.test.tsx`
74. `frontend-modern/src/components/Dashboard/__tests__/useEnhancedCPUBarState.test.tsx`
75. `frontend-modern/src/components/Dashboard/ThresholdSlider.test.tsx`
76. `frontend-modern/src/components/Dashboard/__tests__/useThresholdSliderState.test.ts`
77. `frontend-modern/src/components/Dashboard/__tests__/StackedDiskBar.test.tsx`
78. `frontend-modern/src/components/Dashboard/__tests__/useStackedDiskBarState.test.tsx`
79. `frontend-modern/src/components/Dashboard/StackedMemoryBar.test.tsx`
80. `frontend-modern/src/components/Dashboard/__tests__/useStackedMemoryBarState.test.tsx`
81. `frontend-modern/src/components/Dashboard/__tests__/DiskList.test.tsx`
82. `frontend-modern/src/components/Dashboard/__tests__/GuestRow.test.tsx`
83. `frontend-modern/src/components/Dashboard/GuestDrawer.test.tsx`
84. `frontend-modern/src/components/Dashboard/__tests__/useGroupedTableWindowing.test.ts`
85. `frontend-modern/src/components/Infrastructure/__tests__/UnifiedResourceTable.performance.contract.test.tsx`
## Shared Boundaries
@@ -126,7 +128,7 @@ regression protection.
10. Extend dashboard drawer derivations and runtime wiring through `frontend-modern/src/components/Dashboard/guestDrawerModel.ts` and `frontend-modern/src/components/Dashboard/useGuestDrawerState.ts`, and extend drawer overview rendering through `frontend-modern/src/components/Dashboard/GuestDrawerOverview.tsx`, rather than rebuilding canonical guest identity, discovery routing, or drawer-local normalization inside `frontend-modern/src/components/Dashboard/GuestDrawer.tsx`
11. Extend dashboard disk-list derivations and fallback runtime wiring through `frontend-modern/src/components/Dashboard/diskListModel.ts` and `frontend-modern/src/components/Dashboard/useDiskListState.ts` rather than rebuilding usage math, progress-state mapping, or tooltip fallback logic inside `frontend-modern/src/components/Dashboard/DiskList.tsx`
12. Extend dashboard guest metadata cache persistence, metadata refresh, org-scope switching, and optimistic custom-URL updates through `frontend-modern/src/components/Dashboard/useDashboardGuestMetadataState.ts` rather than rebuilding dashboard-local storage caches, event listeners, or guest metadata API wiring inside `frontend-modern/src/components/Dashboard/useDashboardState.ts`
13. Extend dashboard deep-link selection, hovered-row continuity, and table scroll preservation through `frontend-modern/src/components/Dashboard/useDashboardSelectionState.ts` rather than rebuilding resource-query parsing, selected-row scroll pinning, or hovered-row invalidation inside `frontend-modern/src/components/Dashboard/useDashboardState.ts`
13. Extend dashboard deep-link selection and hovered-row continuity semantics through `frontend-modern/src/components/Dashboard/dashboardSelectionModel.ts`, and extend table scroll preservation plus reactive selection state through `frontend-modern/src/components/Dashboard/useDashboardSelectionState.ts`, rather than rebuilding resource-query parsing, selected-row scroll pinning, or hovered-row invalidation inside `frontend-modern/src/components/Dashboard/useDashboardState.ts`
14. Extend dashboard workload route ownership, route-driven option catalogs, and toolbar filter config through `frontend-modern/src/components/Dashboard/useDashboardWorkloadRouteState.ts`, `frontend-modern/src/components/Dashboard/useDashboardWorkloadFilterOptions.ts`, `frontend-modern/src/components/Dashboard/dashboardWorkloadRouteModel.ts`, `frontend-modern/src/components/Dashboard/dashboardWorkloadFilterConfigModel.ts`, and `frontend-modern/src/components/Dashboard/dashboardWorkloadRouteStateModel.ts`, and extend query-param synchronization plus managed workload URL semantics through `frontend-modern/src/components/Dashboard/useDashboardWorkloadUrlSync.ts` and `frontend-modern/src/components/Dashboard/dashboardWorkloadUrlSyncModel.ts`, rather than rebuilding route sync, alias parsing, option derivation, toolbar callback/config wiring, reset policy, node-selection compatibility rules, param precedence, or managed workload URLs inside `frontend-modern/src/components/Dashboard/useDashboardState.ts`
15. Extend grouped dashboard workload derivation, summary fallbacks, and grouped/windowed table presentation through `frontend-modern/src/components/Dashboard/useDashboardWorkloadDerivedState.ts`, and extend node parent mapping through `frontend-modern/src/components/Dashboard/workloadTopology.ts`, rather than rebuilding grouped selectors, summary snapshot math, or topology lookups inside `frontend-modern/src/components/Dashboard/useDashboardState.ts`
16. Extend dashboard control defaults, persistent view preferences, keyboard reset behavior, column-visibility ownership, and tag-search flow through `frontend-modern/src/components/Dashboard/useDashboardControlsState.ts` and `frontend-modern/src/components/Dashboard/dashboardFilterModel.ts` rather than rebuilding sort/search/grouping state, reset drift, or column-toggle plumbing inside `frontend-modern/src/components/Dashboard/useDashboardState.ts`
@@ -2556,6 +2556,7 @@
"frontend-modern/src/components/Dashboard/Dashboard.tsx",
"frontend-modern/src/components/Dashboard/DashboardFilter.tsx",
"frontend-modern/src/components/Dashboard/dashboardFilterModel.ts",
"frontend-modern/src/components/Dashboard/dashboardSelectionModel.ts",
"frontend-modern/src/components/Dashboard/DashboardStateCards.tsx",
"frontend-modern/src/components/Dashboard/DashboardStatsStrip.tsx",
"frontend-modern/src/components/Dashboard/dashboardWorkloadFilterConfigModel.ts",
@@ -2617,6 +2618,7 @@
"exact_files": [
"frontend-modern/src/components/Dashboard/__tests__/Dashboard.performance.contract.test.tsx",
"frontend-modern/src/components/Dashboard/__tests__/DashboardFilter.test.tsx",
"frontend-modern/src/components/Dashboard/__tests__/dashboardSelectionModel.test.ts",
"frontend-modern/src/components/Dashboard/__tests__/dashboardWorkloadFilterConfigModel.test.ts",
"frontend-modern/src/components/Dashboard/__tests__/dashboardWorkloadRouteModel.test.ts",
"frontend-modern/src/components/Dashboard/__tests__/dashboardWorkloadRouteStateModel.test.ts",
@@ -2667,6 +2669,7 @@
"frontend-modern/src/components/Dashboard/Dashboard.tsx",
"frontend-modern/src/components/Dashboard/DashboardFilter.tsx",
"frontend-modern/src/components/Dashboard/dashboardFilterModel.ts",
"frontend-modern/src/components/Dashboard/dashboardSelectionModel.ts",
"frontend-modern/src/components/Dashboard/DashboardStateCards.tsx",
"frontend-modern/src/components/Dashboard/DashboardStatsStrip.tsx",
"frontend-modern/src/components/Dashboard/dashboardWorkloadFilterConfigModel.ts",
@@ -2724,6 +2727,7 @@
"exact_files": [
"frontend-modern/src/components/Dashboard/__tests__/Dashboard.performance.contract.test.tsx",
"frontend-modern/src/components/Dashboard/__tests__/DashboardFilter.test.tsx",
"frontend-modern/src/components/Dashboard/__tests__/dashboardSelectionModel.test.ts",
"frontend-modern/src/components/Dashboard/__tests__/dashboardWorkloadFilterConfigModel.test.ts",
"frontend-modern/src/components/Dashboard/__tests__/dashboardWorkloadRouteModel.test.ts",
"frontend-modern/src/components/Dashboard/__tests__/dashboardWorkloadRouteStateModel.test.ts",
@@ -13,6 +13,7 @@ import workloadTableHeaderSource from '../WorkloadTableHeader.tsx?raw';
import dashboardFilterModelSource from '../dashboardFilterModel.ts?raw';
import dashboardControlsStateSource from '../useDashboardControlsState.ts?raw';
import dashboardGuestMetadataStateSource from '../useDashboardGuestMetadataState.ts?raw';
import dashboardSelectionModelSource from '../dashboardSelectionModel.ts?raw';
import dashboardSelectionStateSource from '../useDashboardSelectionState.ts?raw';
import dashboardWorkloadDerivedStateSource from '../useDashboardWorkloadDerivedState.ts?raw';
import dashboardWorkloadFilterOptionsSource from '../useDashboardWorkloadFilterOptions.ts?raw';
@@ -649,10 +650,16 @@ describe('Dashboard performance contract', () => {
expect(dashboardWorkloadRouteModelSource).toContain("from './workloadTopology'");
expect(dashboardWorkloadRouteModelSource).toContain('workloadNodeScopeId');
expect(dashboardWorkloadRouteModelSource).toContain('getKubernetesContextKey');
expect(dashboardSelectionStateSource).toContain('parseWorkloadsLinkSearch');
expect(dashboardSelectionStateSource).toContain('const [selectedGuestId, setSelectedGuestIdRaw]');
expect(dashboardSelectionStateSource).toContain('const [hoveredWorkloadId, setHoveredWorkloadId]');
expect(dashboardSelectionStateSource).toContain('setHandledResourceId(null)');
expect(dashboardSelectionStateSource).toContain("from './dashboardSelectionModel'");
expect(dashboardSelectionStateSource).not.toContain('parseWorkloadsLinkSearch');
expect(dashboardSelectionStateSource).not.toContain('getCanonicalWorkloadId');
expect(dashboardSelectionModelSource).toContain('parseWorkloadsLinkSearch(search)');
expect(dashboardSelectionModelSource).toContain('getCanonicalWorkloadId');
expect(dashboardSelectionModelSource).toContain('resolveDashboardResourceSelection');
expect(dashboardSelectionModelSource).toContain('dashboardHasHoveredWorkload');
expect(groupedTableWindowingSource).toContain('DEFAULT_WINDOW_SIZE');
expect(groupedTableWindowingSource).toContain('DEFAULT_ENABLE_THRESHOLD');
expect(groupedTableWindowingSource).toContain('DEFAULT_OVERSCAN_ROWS');
@@ -0,0 +1,38 @@
import { describe, expect, it } from 'vitest';
import type { WorkloadGuest } from '@/types/workloads';
import {
dashboardHasHoveredWorkload,
resolveDashboardResourceSelection,
} from '../dashboardSelectionModel';
describe('dashboardSelectionModel', () => {
it('resolves dashboard resource deep links into selected guest and node scope', () => {
expect(resolveDashboardResourceSelection('?resource=cluster-a:node-1:101')).toEqual({
resourceId: 'cluster-a:node-1:101',
selectedNode: 'cluster-a-node-1',
});
expect(resolveDashboardResourceSelection('?resource=guest-1')).toEqual({
resourceId: 'guest-1',
selectedNode: null,
});
expect(resolveDashboardResourceSelection('')).toBeNull();
});
it('checks hovered workload continuity against canonical workload ids', () => {
const guests = [
{
id: 'cluster-a:node-1:101',
name: 'guest-1',
status: 'running',
instance: 'cluster-a',
node: 'node-1',
vmid: 101,
} as unknown as WorkloadGuest,
];
expect(dashboardHasHoveredWorkload(guests, 'cluster-a:node-1:101')).toBe(true);
expect(dashboardHasHoveredWorkload(guests, 'cluster-a:node-1:102')).toBe(false);
});
});
@@ -4,6 +4,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import type { WorkloadGuest } from '@/types/workloads';
import { resolveDashboardResourceSelection } from '../dashboardSelectionModel';
import { useDashboardSelectionState } from '../useDashboardSelectionState';
let locationSearch = '?resource=cluster-a:node-1:101';
@@ -43,6 +44,7 @@ describe('useDashboardSelectionState', () => {
expect(result.selectedGuestId()).toBe('cluster-a:node-1:101');
expect(setSelectedNode).toHaveBeenCalledWith('cluster-a-node-1');
expect(resolveDashboardResourceSelection(locationSearch)?.selectedNode).toBe('cluster-a-node-1');
});
it('clears stale hovered workload ids when filtered guests change', () => {
@@ -0,0 +1,28 @@
import { parseWorkloadsLinkSearch } from '@/routing/resourceLinks';
import type { WorkloadGuest } from '@/types/workloads';
import { getCanonicalWorkloadId } from '@/utils/workloads';
export interface DashboardResourceSelection {
resourceId: string;
selectedNode: string | null;
}
export const resolveDashboardResourceSelection = (
search: string,
): DashboardResourceSelection | null => {
const { resource: resourceId } = parseWorkloadsLinkSearch(search);
if (!resourceId) return null;
const [instance, node, vmid] = resourceId.split(':');
const selectedNode = instance && node && vmid ? `${instance}-${node}` : null;
return {
resourceId,
selectedNode,
};
};
export const dashboardHasHoveredWorkload = (
filteredGuests: WorkloadGuest[],
hoveredId: string,
): boolean => filteredGuests.some((guest) => getCanonicalWorkloadId(guest) === hoveredId);
@@ -1,10 +1,11 @@
import { useLocation } from '@solidjs/router';
import { createEffect, createSignal, type Accessor } from 'solid-js';
import { parseWorkloadsLinkSearch } from '@/routing/resourceLinks';
import { getCanonicalWorkloadId } from '@/utils/workloads';
import type { WorkloadGuest } from '@/types/workloads';
import {
dashboardHasHoveredWorkload,
resolveDashboardResourceSelection,
} from './dashboardSelectionModel';
interface UseDashboardSelectionStateOptions {
filteredGuests: Accessor<WorkloadGuest[]>;
@@ -48,21 +49,20 @@ export function useDashboardSelectionState(options: UseDashboardSelectionStateOp
};
createEffect(() => {
const { resource: resourceId } = parseWorkloadsLinkSearch(location.search);
if (!resourceId) {
const selection = resolveDashboardResourceSelection(location.search);
if (!selection) {
if (handledResourceId() !== null) {
setHandledResourceId(null);
}
return;
}
const { resourceId, selectedNode } = selection;
if (resourceId === handledResourceId()) return;
setSelectedGuestId(resourceId);
const [instance, node, vmid] = resourceId.split(':');
if (instance && node && vmid) {
options.setSelectedNode(`${instance}-${node}`);
if (selectedNode) {
options.setSelectedNode(selectedNode);
}
setHandledResourceId(resourceId);
});
@@ -70,10 +70,7 @@ export function useDashboardSelectionState(options: UseDashboardSelectionStateOp
createEffect(() => {
const hoveredId = hoveredWorkloadId();
if (!hoveredId) return;
const exists = options
.filteredGuests()
.some((guest) => getCanonicalWorkloadId(guest) === hoveredId);
if (!exists) {
if (!dashboardHasHoveredWorkload(options.filteredGuests(), hoveredId)) {
setHoveredWorkloadId(null);
}
});
@@ -89,6 +89,7 @@ import workloadTableHeaderSource from '@/components/Dashboard/WorkloadTableHeade
import dashboardFilterModelSource from '@/components/Dashboard/dashboardFilterModel.ts?raw';
import dashboardControlsStateSource from '@/components/Dashboard/useDashboardControlsState.ts?raw';
import dashboardGuestMetadataStateSource from '@/components/Dashboard/useDashboardGuestMetadataState.ts?raw';
import dashboardSelectionModelSource from '@/components/Dashboard/dashboardSelectionModel.ts?raw';
import dashboardSelectionStateSource from '@/components/Dashboard/useDashboardSelectionState.ts?raw';
import dashboardWorkloadDerivedStateSource from '@/components/Dashboard/useDashboardWorkloadDerivedState.ts?raw';
import dashboardWorkloadFilterOptionsSource from '@/components/Dashboard/useDashboardWorkloadFilterOptions.ts?raw';
@@ -668,10 +669,16 @@ describe('frontend resource type boundaries', () => {
expect(dashboardWorkloadRouteModelSource).toContain('workloadNodeScopeId');
expect(dashboardWorkloadRouteModelSource).toContain('getKubernetesContextKey');
expect(dashboardWorkloadRouteStateSource).toContain('isWorkloadsRoute,');
expect(dashboardSelectionStateSource).toContain('parseWorkloadsLinkSearch');
expect(dashboardSelectionStateSource).toContain('const [selectedGuestId, setSelectedGuestIdRaw]');
expect(dashboardSelectionStateSource).toContain('const [hoveredWorkloadId, setHoveredWorkloadId]');
expect(dashboardSelectionStateSource).toContain('setHandledResourceId(null)');
expect(dashboardSelectionStateSource).toContain("from './dashboardSelectionModel'");
expect(dashboardSelectionStateSource).not.toContain('parseWorkloadsLinkSearch');
expect(dashboardSelectionStateSource).not.toContain('getCanonicalWorkloadId');
expect(dashboardSelectionModelSource).toContain('parseWorkloadsLinkSearch(search)');
expect(dashboardSelectionModelSource).toContain('getCanonicalWorkloadId');
expect(dashboardSelectionModelSource).toContain('resolveDashboardResourceSelection');
expect(dashboardSelectionModelSource).toContain('dashboardHasHoveredWorkload');
expect(dashboardStateSource).not.toContain('const guestId = () => {');
expect(dashboardFilterSource).toContain('useDashboardFilterState');
expect(dashboardFilterSource).not.toContain('const [filtersOpen, setFiltersOpen] =');
@@ -1970,6 +1970,36 @@ class SubsystemLookupTest(unittest.TestCase):
"dashboard-workload-hot-path",
)
def test_lookup_paths_assigns_dashboard_selection_runtime_to_performance_and_scalability(
self,
) -> None:
result = lookup_paths(
[
"frontend-modern/src/components/Dashboard/dashboardSelectionModel.ts",
"frontend-modern/src/components/Dashboard/useDashboardSelectionState.ts",
]
)
self.assertEqual(result["unowned_runtime_files"], [])
self.assertEqual(
{item["subsystem"] for item in result["impacted_subsystems"]},
{"performance-and-scalability"},
)
for file_entry in result["files"]:
self.assertEqual(file_entry["classification"], "runtime")
self.assertEqual(
{match["subsystem"] for match in file_entry["matches"]},
{"performance-and-scalability"},
)
match = file_entry["matches"][0]
self.assertEqual(
match["contract"],
"docs/release-control/v6/internal/subsystems/performance-and-scalability.md",
)
self.assertEqual(
match["verification_requirement"]["id"],
"dashboard-workload-hot-path",
)
def test_lookup_paths_assigns_dashboard_workload_table_runtime_to_performance_and_scalability(
self,
) -> None: