Split dashboard workload URL sync owners

This commit is contained in:
rcourtman
2026-03-22 00:47:56 +00:00
parent 016e000d22
commit 934052eec8
8 changed files with 343 additions and 115 deletions
@@ -71,32 +71,34 @@ regression protection.
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/useDashboardWorkloadFilterOptions.ts`
53. `frontend-modern/src/components/Dashboard/__tests__/dashboardWorkloadRouteModel.test.ts`
54. `frontend-modern/src/components/Dashboard/__tests__/workloadTopology.test.ts`
55. `frontend-modern/src/components/Infrastructure/UnifiedResourceTable.tsx`
56. `frontend-modern/src/components/Infrastructure/useUnifiedResourceTableState.ts`
57. `frontend-modern/src/components/Infrastructure/infrastructureSelectors.ts`
58. `frontend-modern/src/components/Infrastructure/resourceDetailMappers.ts`
59. `frontend-modern/src/components/Dashboard/__tests__/Dashboard.performance.contract.test.tsx`
60. `frontend-modern/src/components/Dashboard/__tests__/DashboardFilter.test.tsx`
61. `frontend-modern/src/components/Dashboard/__tests__/useDashboardFilterState.test.ts`
62. `frontend-modern/src/components/Dashboard/__tests__/useDashboardSelectionState.test.ts`
63. `frontend-modern/src/components/Dashboard/MetricBar.test.tsx`
64. `frontend-modern/src/components/Dashboard/__tests__/useMetricBarState.test.tsx`
65. `frontend-modern/src/components/Dashboard/__tests__/EnhancedCPUBar.test.tsx`
66. `frontend-modern/src/components/Dashboard/__tests__/useEnhancedCPUBarState.test.tsx`
67. `frontend-modern/src/components/Dashboard/ThresholdSlider.test.tsx`
68. `frontend-modern/src/components/Dashboard/__tests__/useThresholdSliderState.test.ts`
69. `frontend-modern/src/components/Dashboard/__tests__/StackedDiskBar.test.tsx`
70. `frontend-modern/src/components/Dashboard/__tests__/useStackedDiskBarState.test.tsx`
71. `frontend-modern/src/components/Dashboard/StackedMemoryBar.test.tsx`
72. `frontend-modern/src/components/Dashboard/__tests__/useStackedMemoryBarState.test.tsx`
73. `frontend-modern/src/components/Dashboard/__tests__/DiskList.test.tsx`
74. `frontend-modern/src/components/Dashboard/__tests__/GuestRow.test.tsx`
75. `frontend-modern/src/components/Dashboard/GuestDrawer.test.tsx`
76. `frontend-modern/src/components/Dashboard/__tests__/useGroupedTableWindowing.test.ts`
77. `frontend-modern/src/components/Infrastructure/__tests__/UnifiedResourceTable.performance.contract.test.tsx`
52. `frontend-modern/src/components/Dashboard/dashboardWorkloadUrlSyncModel.ts`
53. `frontend-modern/src/components/Dashboard/useDashboardWorkloadFilterOptions.ts`
54. `frontend-modern/src/components/Dashboard/__tests__/dashboardWorkloadRouteModel.test.ts`
55. `frontend-modern/src/components/Dashboard/__tests__/dashboardWorkloadUrlSyncModel.test.ts`
56. `frontend-modern/src/components/Dashboard/__tests__/workloadTopology.test.ts`
57. `frontend-modern/src/components/Infrastructure/UnifiedResourceTable.tsx`
58. `frontend-modern/src/components/Infrastructure/useUnifiedResourceTableState.ts`
59. `frontend-modern/src/components/Infrastructure/infrastructureSelectors.ts`
60. `frontend-modern/src/components/Infrastructure/resourceDetailMappers.ts`
61. `frontend-modern/src/components/Dashboard/__tests__/Dashboard.performance.contract.test.tsx`
62. `frontend-modern/src/components/Dashboard/__tests__/DashboardFilter.test.tsx`
63. `frontend-modern/src/components/Dashboard/__tests__/useDashboardFilterState.test.ts`
64. `frontend-modern/src/components/Dashboard/__tests__/useDashboardSelectionState.test.ts`
65. `frontend-modern/src/components/Dashboard/MetricBar.test.tsx`
66. `frontend-modern/src/components/Dashboard/__tests__/useMetricBarState.test.tsx`
67. `frontend-modern/src/components/Dashboard/__tests__/EnhancedCPUBar.test.tsx`
68. `frontend-modern/src/components/Dashboard/__tests__/useEnhancedCPUBarState.test.tsx`
69. `frontend-modern/src/components/Dashboard/ThresholdSlider.test.tsx`
70. `frontend-modern/src/components/Dashboard/__tests__/useThresholdSliderState.test.ts`
71. `frontend-modern/src/components/Dashboard/__tests__/StackedDiskBar.test.tsx`
72. `frontend-modern/src/components/Dashboard/__tests__/useStackedDiskBarState.test.tsx`
73. `frontend-modern/src/components/Dashboard/StackedMemoryBar.test.tsx`
74. `frontend-modern/src/components/Dashboard/__tests__/useStackedMemoryBarState.test.tsx`
75. `frontend-modern/src/components/Dashboard/__tests__/DiskList.test.tsx`
76. `frontend-modern/src/components/Dashboard/__tests__/GuestRow.test.tsx`
77. `frontend-modern/src/components/Dashboard/GuestDrawer.test.tsx`
78. `frontend-modern/src/components/Dashboard/__tests__/useGroupedTableWindowing.test.ts`
79. `frontend-modern/src/components/Infrastructure/__tests__/UnifiedResourceTable.performance.contract.test.tsx`
## Shared Boundaries
@@ -121,7 +123,7 @@ regression protection.
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`
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`, and `frontend-modern/src/components/Dashboard/dashboardWorkloadRouteModel.ts`, and extend query-param synchronization through `frontend-modern/src/components/Dashboard/useDashboardWorkloadUrlSync.ts`, rather than rebuilding route sync, alias parsing, option derivation, or managed workload URLs 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`, and `frontend-modern/src/components/Dashboard/dashboardWorkloadRouteModel.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, 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`
17. Extend dashboard filter active-count, reset semantics, and mobile toolbar state through `frontend-modern/src/components/Dashboard/dashboardFilterModel.ts` and `frontend-modern/src/components/Dashboard/useDashboardFilterState.ts`, rather than rebuilding filter-local state inside `frontend-modern/src/components/Dashboard/DashboardFilter.tsx`
@@ -2560,6 +2560,7 @@
"frontend-modern/src/components/Dashboard/DashboardStatsStrip.tsx",
"frontend-modern/src/components/Dashboard/dashboardWorkloadRouteModel.ts",
"frontend-modern/src/components/Dashboard/DashboardWorkloadTable.tsx",
"frontend-modern/src/components/Dashboard/dashboardWorkloadUrlSyncModel.ts",
"frontend-modern/src/components/Dashboard/DiskList.tsx",
"frontend-modern/src/components/Dashboard/diskListModel.ts",
"frontend-modern/src/components/Dashboard/EnhancedCPUBar.tsx",
@@ -2615,6 +2616,7 @@
"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__/dashboardWorkloadRouteModel.test.ts",
"frontend-modern/src/components/Dashboard/__tests__/dashboardWorkloadUrlSyncModel.test.ts",
"frontend-modern/src/components/Dashboard/__tests__/DiskList.test.tsx",
"frontend-modern/src/components/Dashboard/__tests__/EnhancedCPUBar.test.tsx",
"frontend-modern/src/components/Dashboard/__tests__/StackedDiskBar.test.tsx",
@@ -2665,6 +2667,7 @@
"frontend-modern/src/components/Dashboard/DashboardStatsStrip.tsx",
"frontend-modern/src/components/Dashboard/dashboardWorkloadRouteModel.ts",
"frontend-modern/src/components/Dashboard/DashboardWorkloadTable.tsx",
"frontend-modern/src/components/Dashboard/dashboardWorkloadUrlSyncModel.ts",
"frontend-modern/src/components/Dashboard/DiskList.tsx",
"frontend-modern/src/components/Dashboard/diskListModel.ts",
"frontend-modern/src/components/Dashboard/EnhancedCPUBar.tsx",
@@ -2716,6 +2719,7 @@
"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__/dashboardWorkloadRouteModel.test.ts",
"frontend-modern/src/components/Dashboard/__tests__/dashboardWorkloadUrlSyncModel.test.ts",
"frontend-modern/src/components/Dashboard/__tests__/DiskList.test.tsx",
"frontend-modern/src/components/Dashboard/__tests__/EnhancedCPUBar.test.tsx",
"frontend-modern/src/components/Dashboard/__tests__/GuestRow.test.tsx",
@@ -17,6 +17,7 @@ import dashboardSelectionStateSource from '../useDashboardSelectionState.ts?raw'
import dashboardWorkloadDerivedStateSource from '../useDashboardWorkloadDerivedState.ts?raw';
import dashboardWorkloadFilterOptionsSource from '../useDashboardWorkloadFilterOptions.ts?raw';
import dashboardWorkloadRouteModelSource from '../dashboardWorkloadRouteModel.ts?raw';
import dashboardWorkloadUrlSyncModelSource from '../dashboardWorkloadUrlSyncModel.ts?raw';
import dashboardWorkloadRouteStateSource from '../useDashboardWorkloadRouteState.ts?raw';
import dashboardWorkloadUrlSyncSource from '../useDashboardWorkloadUrlSync.ts?raw';
import dashboardStateSource from '../useDashboardState.ts?raw';
@@ -558,15 +559,33 @@ describe('Dashboard performance contract', () => {
expect(dashboardWorkloadRouteModelSource).toContain(
"normalizeWorkloadViewModeParam(raw) ?? 'all'",
);
expect(dashboardWorkloadUrlSyncSource).toContain('buildWorkloadsPath({');
expect(dashboardWorkloadUrlSyncSource).toContain('normalizeWorkloadViewModeParam');
expect(dashboardWorkloadUrlSyncSource).not.toContain('buildWorkloadsPath({');
expect(dashboardWorkloadUrlSyncSource).not.toContain('normalizeWorkloadViewModeParam');
expect(dashboardWorkloadUrlSyncSource).not.toContain('parseWorkloadsLinkSearch');
expect(dashboardWorkloadUrlSyncSource).toContain(
"from './dashboardWorkloadRouteModel'",
);
expect(dashboardWorkloadUrlSyncSource).toContain(
"from './dashboardWorkloadUrlSyncModel'",
);
expect(dashboardWorkloadUrlSyncSource).toContain(
'const [handledTypeParam, setHandledTypeParam]',
);
expect(dashboardWorkloadUrlSyncSource).toContain('parseWorkloadsLinkSearch');
expect(dashboardWorkloadUrlSyncSource).toContain('parseDashboardWorkloadUrlParams');
expect(dashboardWorkloadUrlSyncSource).toContain(
'resolveDashboardManagedWorkloadsNavigateTarget({',
);
expect(dashboardWorkloadUrlSyncModelSource).toContain('parseWorkloadsLinkSearch(search)');
expect(dashboardWorkloadUrlSyncModelSource).toContain('buildWorkloadsPath({');
expect(dashboardWorkloadUrlSyncModelSource).toContain(
'resolveDashboardManagedWorkloadsNavigateTarget',
);
expect(dashboardWorkloadUrlSyncModelSource).toContain(
'resolveDashboardWorkloadRuntimeParam',
);
expect(dashboardWorkloadUrlSyncModelSource).toContain(
'normalizeWorkloadViewModeParam(params.type)',
);
expect(dashboardControlsStateSource).toContain('useBreakpoint');
expect(dashboardControlsStateSource).toContain('useColumnVisibility');
expect(dashboardControlsStateSource).toContain('usePersistentSignal');
@@ -0,0 +1,106 @@
import { describe, expect, it } from 'vitest';
import {
parseDashboardWorkloadUrlParams,
resolveDashboardManagedWorkloadsNavigateTarget,
resolveDashboardWorkloadRuntimeParam,
resolveDashboardWorkloadTypeParam,
} from '../dashboardWorkloadUrlSyncModel';
describe('dashboardWorkloadUrlSyncModel', () => {
it('parses canonical workload route params and keeps resource deep links intact', () => {
expect(
parseDashboardWorkloadUrlParams(
'?type=docker&runtime=containerd&context=prod&namespace=default&agent=node-a&resource=guest-1',
),
).toEqual({
type: 'docker',
runtime: 'containerd',
context: 'prod',
namespace: 'default',
agent: 'node-a',
resource: 'guest-1',
});
});
it('resolves workload type params through the canonical alias rules and k8s precedence', () => {
expect(
resolveDashboardWorkloadTypeParam({
type: 'docker',
runtime: '',
context: '',
namespace: '',
agent: '',
resource: '',
}),
).toBe('app-container');
expect(
resolveDashboardWorkloadTypeParam({
type: 'vm',
runtime: '',
context: 'prod',
namespace: '',
agent: '',
resource: '',
}),
).toBeNull();
});
it('only applies runtime params when the url semantics still resolve to app-container scope', () => {
expect(
resolveDashboardWorkloadRuntimeParam({
type: 'docker',
runtime: 'containerd',
context: '',
namespace: '',
agent: '',
resource: '',
}),
).toEqual({
forceViewMode: 'app-container',
runtime: 'containerd',
shouldApply: true,
});
expect(
resolveDashboardWorkloadRuntimeParam({
type: 'vm',
runtime: 'containerd',
context: 'prod',
namespace: '',
agent: '',
resource: '',
}),
).toEqual({
forceViewMode: null,
runtime: 'containerd',
shouldApply: false,
});
});
it('builds managed workload navigate targets without dropping unrelated resource params', () => {
expect(
resolveDashboardManagedWorkloadsNavigateTarget({
currentSearch: '?resource=guest-1&type=vm&agent=node-a',
viewMode: 'pod',
containerRuntime: 'docker',
selectedKubernetesContext: 'prod',
selectedKubernetesNamespace: 'default',
selectedNode: 'cluster-a-node-a',
selectedHostHint: null,
}),
).toBe('/workloads?resource=guest-1&type=pod&context=prod&namespace=default');
expect(
resolveDashboardManagedWorkloadsNavigateTarget({
currentSearch: '?type=pod&context=prod&namespace=default',
viewMode: 'pod',
containerRuntime: '',
selectedKubernetesContext: 'prod',
selectedKubernetesNamespace: 'default',
selectedNode: null,
selectedHostHint: null,
}),
).toBeNull();
});
});
@@ -0,0 +1,122 @@
import type { ViewMode } from '@/types/workloads';
import {
buildWorkloadsPath,
parseWorkloadsLinkSearch,
WORKLOADS_PATH,
WORKLOADS_QUERY_PARAMS,
} from '@/routing/resourceLinks';
import { areSearchParamsEquivalent } from '@/utils/searchParams';
import { normalizeWorkloadViewModeParam } from '@/utils/workloads';
export interface DashboardWorkloadUrlParams {
type: string;
runtime: string;
context: string;
namespace: string;
agent: string;
resource: string;
}
export interface DashboardWorkloadRuntimeParamResolution {
forceViewMode: ViewMode | null;
runtime: string;
shouldApply: boolean;
}
interface DashboardManagedWorkloadsNavigateTargetOptions {
containerRuntime: string;
currentSearch: string;
selectedHostHint: string | null;
selectedKubernetesContext: string | null;
selectedKubernetesNamespace: string | null;
selectedNode: string | null;
viewMode: ViewMode;
}
export const parseDashboardWorkloadUrlParams = (search: string): DashboardWorkloadUrlParams =>
parseWorkloadsLinkSearch(search);
const hasDashboardWorkloadKubernetesScope = (params: DashboardWorkloadUrlParams): boolean =>
Boolean(params.context.trim()) || Boolean(params.namespace.trim());
export const resolveDashboardWorkloadTypeParam = (
params: DashboardWorkloadUrlParams,
): ViewMode | null => {
const nextMode = normalizeWorkloadViewModeParam(params.type);
if (!nextMode) return null;
if (hasDashboardWorkloadKubernetesScope(params) && nextMode !== 'pod') return null;
return nextMode;
};
export const resolveDashboardWorkloadRuntimeParam = (
params: DashboardWorkloadUrlParams,
): DashboardWorkloadRuntimeParamResolution => {
const nextMode = resolveDashboardWorkloadTypeParam(params);
const runtimeRelevant =
!hasDashboardWorkloadKubernetesScope(params) &&
(nextMode === 'app-container' || !params.type.trim());
if (!runtimeRelevant) {
return {
forceViewMode: null,
runtime: params.runtime,
shouldApply: false,
};
}
if (!params.runtime.trim()) {
return {
forceViewMode: null,
runtime: '',
shouldApply: true,
};
}
return {
forceViewMode: 'app-container',
runtime: params.runtime,
shouldApply: true,
};
};
export const resolveDashboardManagedWorkloadsNavigateTarget = ({
containerRuntime,
currentSearch,
selectedHostHint,
selectedKubernetesContext,
selectedKubernetesNamespace,
selectedNode,
viewMode,
}: DashboardManagedWorkloadsNavigateTargetOptions): string | null => {
const currentParams = new URLSearchParams(currentSearch);
const nextParams = new URLSearchParams(currentSearch);
const nextType = viewMode === 'all' ? '' : viewMode;
const nextRuntime = viewMode === 'app-container' ? containerRuntime.trim() : '';
const nextContext = viewMode === 'pod' ? (selectedKubernetesContext ?? '') : '';
const nextNamespace = viewMode === 'pod' ? (selectedKubernetesNamespace ?? '') : '';
const nextAgent = viewMode === 'pod' ? '' : (selectedNode ?? selectedHostHint ?? '');
const managedPath = buildWorkloadsPath({
type: nextType || null,
runtime: nextRuntime || null,
context: nextContext || null,
namespace: nextNamespace || null,
agent: nextAgent || null,
});
const managedUrl = new URL(managedPath, 'http://pulse.local');
nextParams.delete(WORKLOADS_QUERY_PARAMS.type);
nextParams.delete(WORKLOADS_QUERY_PARAMS.runtime);
nextParams.delete(WORKLOADS_QUERY_PARAMS.context);
nextParams.delete(WORKLOADS_QUERY_PARAMS.namespace);
nextParams.delete(WORKLOADS_QUERY_PARAMS.agent);
managedUrl.searchParams.forEach((value, key) => {
nextParams.set(key, value);
});
if (areSearchParamsEquivalent(currentParams, nextParams)) {
return null;
}
const nextSearch = nextParams.toString();
return nextSearch ? `${WORKLOADS_PATH}?${nextSearch}` : WORKLOADS_PATH;
};
@@ -1,4 +1,5 @@
import {
createMemo,
createEffect,
createSignal,
onCleanup,
@@ -9,14 +10,15 @@ import {
import { useLocation, useNavigate } from '@solidjs/router';
import type { ViewMode } from '@/types/workloads';
import {
buildWorkloadsPath,
parseWorkloadsLinkSearch,
WORKLOADS_PATH,
WORKLOADS_QUERY_PARAMS,
} from '@/routing/resourceLinks';
import { areSearchParamsEquivalent } from '@/utils/searchParams';
import { normalizeWorkloadViewModeParam } from '@/utils/workloads';
import type { DashboardWorkloadNodeOption } from './dashboardWorkloadRouteModel';
import {
parseDashboardWorkloadUrlParams,
resolveDashboardManagedWorkloadsNavigateTarget,
resolveDashboardWorkloadRuntimeParam,
resolveDashboardWorkloadTypeParam,
} from './dashboardWorkloadUrlSyncModel';
export interface DashboardWorkloadUrlSyncOptions {
containerRuntime: Accessor<string>;
@@ -42,6 +44,7 @@ export function useDashboardWorkloadUrlSync(options: DashboardWorkloadUrlSyncOpt
const navigate = useNavigate();
const location = useLocation();
const isWorkloadsRoute = () => location.pathname === WORKLOADS_PATH;
const workloadUrlParams = createMemo(() => parseDashboardWorkloadUrlParams(location.search));
const [handledTypeParam, setHandledTypeParam] = createSignal('');
const [handledRuntimeParam, setHandledRuntimeParam] = createSignal('');
@@ -143,9 +146,8 @@ export function useDashboardWorkloadUrlSync(options: DashboardWorkloadUrlSyncOpt
});
createEffect(() => {
const parsed = parseWorkloadsLinkSearch(location.search);
const typeParam = parsed.type;
const normalizedType = typeParam ?? '';
const parsed = workloadUrlParams();
const normalizedType = parsed.type;
if (normalizedType === handledTypeParam()) return;
if (!normalizedType) {
@@ -153,25 +155,18 @@ export function useDashboardWorkloadUrlSync(options: DashboardWorkloadUrlSyncOpt
return;
}
const hasK8sScope =
Boolean((parsed.context ?? '').trim()) || Boolean((parsed.namespace ?? '').trim());
const nextMode = normalizeWorkloadViewModeParam(normalizedType);
const nextMode = resolveDashboardWorkloadTypeParam(parsed);
if (!nextMode) {
setHandledTypeParam(normalizedType);
return;
}
if (hasK8sScope && nextMode !== 'pod') {
setHandledTypeParam(normalizedType);
return;
}
options.setViewMode(nextMode);
setHandledTypeParam(normalizedType);
});
createEffect(() => {
const { context: contextParam } = parseWorkloadsLinkSearch(location.search);
const normalized = contextParam ?? '';
const normalized = workloadUrlParams().context;
if (normalized === handledContextParam()) return;
if (normalized) {
@@ -191,8 +186,7 @@ export function useDashboardWorkloadUrlSync(options: DashboardWorkloadUrlSyncOpt
});
createEffect(() => {
const { namespace: namespaceParam } = parseWorkloadsLinkSearch(location.search);
const normalized = namespaceParam ?? '';
const normalized = workloadUrlParams().namespace;
if (normalized === handledNamespaceParam()) return;
if (normalized) {
@@ -212,8 +206,7 @@ export function useDashboardWorkloadUrlSync(options: DashboardWorkloadUrlSyncOpt
});
createEffect(() => {
const { agent: agentParam } = parseWorkloadsLinkSearch(location.search);
const normalized = agentParam ?? '';
const normalized = workloadUrlParams().agent;
if (normalized === handledAgentParam()) return;
if (normalized) {
@@ -233,34 +226,21 @@ export function useDashboardWorkloadUrlSync(options: DashboardWorkloadUrlSyncOpt
});
createEffect(() => {
const parsed = parseWorkloadsLinkSearch(location.search);
const urlRuntime = parsed.runtime ?? '';
const parsed = workloadUrlParams();
const urlRuntime = parsed.runtime;
if (urlRuntime === handledRuntimeParam()) return;
const urlContext = parsed.context ?? '';
const hasContext = Boolean(urlContext.trim());
const hasNamespace = Boolean((parsed.namespace ?? '').trim());
const urlType = parsed.type ?? '';
const nextMode = normalizeWorkloadViewModeParam(urlType);
const runtimeRelevant =
!hasContext && !hasNamespace && (nextMode === 'app-container' || !urlType.trim());
if (!runtimeRelevant) {
const resolution = resolveDashboardWorkloadRuntimeParam(parsed);
if (!resolution.shouldApply) {
setHandledRuntimeParam(urlRuntime);
return;
}
if (!urlRuntime.trim()) {
options.setContainerRuntime('');
setHandledRuntimeParam('');
return;
if (resolution.forceViewMode && options.viewMode() !== resolution.forceViewMode) {
options.setViewMode(resolution.forceViewMode);
}
if (options.viewMode() !== 'app-container') {
options.setViewMode('app-container');
}
options.setContainerRuntime(urlRuntime);
if (!options.showFilters()) {
options.setContainerRuntime(resolution.runtime);
if (resolution.runtime && !options.showFilters()) {
options.setShowFilters(true);
}
setHandledRuntimeParam(urlRuntime);
@@ -269,13 +249,13 @@ export function useDashboardWorkloadUrlSync(options: DashboardWorkloadUrlSyncOpt
createEffect(() => {
if (!isWorkloadsRoute()) return;
const parsed = parseWorkloadsLinkSearch(location.search);
const urlType = parsed.type ?? '';
const urlRuntime = parsed.runtime ?? '';
const urlContext = parsed.context ?? '';
const urlNamespace = parsed.namespace ?? '';
const urlAgent = parsed.agent ?? '';
const urlResource = parsed.resource ?? '';
const parsed = workloadUrlParams();
const urlType = parsed.type;
const urlRuntime = parsed.runtime;
const urlContext = parsed.context;
const urlNamespace = parsed.namespace;
const urlAgent = parsed.agent;
const urlResource = parsed.resource;
if (handledTypeParam() !== urlType) return;
if (handledRuntimeParam() !== urlRuntime) return;
@@ -284,40 +264,16 @@ export function useDashboardWorkloadUrlSync(options: DashboardWorkloadUrlSyncOpt
if (handledAgentParam() !== urlAgent) return;
if (urlResource) return;
const currentParams = new URLSearchParams(location.search);
const nextParams = new URLSearchParams(location.search);
const nextType = options.viewMode() === 'all' ? '' : options.viewMode();
const nextRuntime =
options.viewMode() === 'app-container' ? options.containerRuntime().trim() : '';
const nextContext =
options.viewMode() === 'pod' ? (options.selectedKubernetesContext() ?? '') : '';
const nextNamespace =
options.viewMode() === 'pod' ? (options.selectedKubernetesNamespace() ?? '') : '';
const nextAgent =
options.viewMode() === 'pod'
? ''
: (options.selectedNode() ?? options.selectedHostHint() ?? '');
const managedPath = buildWorkloadsPath({
type: nextType || null,
runtime: nextRuntime || null,
context: nextContext || null,
namespace: nextNamespace || null,
agent: nextAgent || null,
const nextPath = resolveDashboardManagedWorkloadsNavigateTarget({
currentSearch: location.search,
viewMode: options.viewMode(),
containerRuntime: options.containerRuntime(),
selectedKubernetesContext: options.selectedKubernetesContext(),
selectedKubernetesNamespace: options.selectedKubernetesNamespace(),
selectedNode: options.selectedNode(),
selectedHostHint: options.selectedHostHint(),
});
const managedUrl = new URL(managedPath, 'http://pulse.local');
nextParams.delete(WORKLOADS_QUERY_PARAMS.type);
nextParams.delete(WORKLOADS_QUERY_PARAMS.runtime);
nextParams.delete(WORKLOADS_QUERY_PARAMS.context);
nextParams.delete(WORKLOADS_QUERY_PARAMS.namespace);
nextParams.delete(WORKLOADS_QUERY_PARAMS.agent);
managedUrl.searchParams.forEach((value, key) => {
nextParams.set(key, value);
});
if (!areSearchParamsEquivalent(currentParams, nextParams)) {
const nextSearch = nextParams.toString();
const nextPath = nextSearch ? `${WORKLOADS_PATH}?${nextSearch}` : WORKLOADS_PATH;
if (nextPath) {
scheduleUrlSyncNavigate(nextPath);
}
});
@@ -93,6 +93,7 @@ import dashboardSelectionStateSource from '@/components/Dashboard/useDashboardSe
import dashboardWorkloadDerivedStateSource from '@/components/Dashboard/useDashboardWorkloadDerivedState.ts?raw';
import dashboardWorkloadFilterOptionsSource from '@/components/Dashboard/useDashboardWorkloadFilterOptions.ts?raw';
import dashboardWorkloadRouteModelSource from '@/components/Dashboard/dashboardWorkloadRouteModel.ts?raw';
import dashboardWorkloadUrlSyncModelSource from '@/components/Dashboard/dashboardWorkloadUrlSyncModel.ts?raw';
import dashboardWorkloadRouteStateSource from '@/components/Dashboard/useDashboardWorkloadRouteState.ts?raw';
import dashboardWorkloadUrlSyncSource from '@/components/Dashboard/useDashboardWorkloadUrlSync.ts?raw';
import dashboardStateSource from '@/components/Dashboard/useDashboardState.ts?raw';
@@ -566,13 +567,29 @@ describe('frontend resource type boundaries', () => {
'const containerRuntimeFilterConfig = createMemo',
);
expect(dashboardWorkloadRouteStateSource).not.toContain('const [handledTypeParam, setHandledTypeParam]');
expect(dashboardWorkloadUrlSyncSource).toContain('buildWorkloadsPath({');
expect(dashboardWorkloadUrlSyncSource).toContain('normalizeWorkloadViewModeParam');
expect(dashboardWorkloadUrlSyncSource).not.toContain('buildWorkloadsPath({');
expect(dashboardWorkloadUrlSyncSource).not.toContain('normalizeWorkloadViewModeParam');
expect(dashboardWorkloadUrlSyncSource).not.toContain('parseWorkloadsLinkSearch');
expect(dashboardWorkloadUrlSyncSource).toContain("from './dashboardWorkloadRouteModel'");
expect(dashboardWorkloadUrlSyncSource).toContain("from './dashboardWorkloadUrlSyncModel'");
expect(dashboardWorkloadUrlSyncSource).toContain(
'const [handledTypeParam, setHandledTypeParam]',
);
expect(dashboardWorkloadUrlSyncSource).toContain('parseWorkloadsLinkSearch');
expect(dashboardWorkloadUrlSyncSource).toContain('parseDashboardWorkloadUrlParams');
expect(dashboardWorkloadUrlSyncSource).toContain(
'resolveDashboardManagedWorkloadsNavigateTarget({',
);
expect(dashboardWorkloadUrlSyncModelSource).toContain('parseWorkloadsLinkSearch(search)');
expect(dashboardWorkloadUrlSyncModelSource).toContain('buildWorkloadsPath({');
expect(dashboardWorkloadUrlSyncModelSource).toContain(
'resolveDashboardManagedWorkloadsNavigateTarget',
);
expect(dashboardWorkloadUrlSyncModelSource).toContain(
'resolveDashboardWorkloadRuntimeParam',
);
expect(dashboardWorkloadUrlSyncModelSource).toContain(
'normalizeWorkloadViewModeParam(params.type)',
);
expect(dashboardControlsStateSource).toContain('useBreakpoint');
expect(dashboardControlsStateSource).toContain('useColumnVisibility');
expect(dashboardControlsStateSource).toContain('usePersistentSignal');
@@ -1941,8 +1941,10 @@ class SubsystemLookupTest(unittest.TestCase):
result = lookup_paths(
[
"frontend-modern/src/components/Dashboard/dashboardWorkloadRouteModel.ts",
"frontend-modern/src/components/Dashboard/dashboardWorkloadUrlSyncModel.ts",
"frontend-modern/src/components/Dashboard/useDashboardWorkloadFilterOptions.ts",
"frontend-modern/src/components/Dashboard/useDashboardWorkloadRouteState.ts",
"frontend-modern/src/components/Dashboard/useDashboardWorkloadUrlSync.ts",
]
)
self.assertEqual(result["unowned_runtime_files"], [])