mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-08-24 00:14:39 +00:00
Keep inline drawer expansion local
This commit is contained in:
parent
34d53b23ea
commit
86ac84f68e
10 changed files with 152 additions and 171 deletions
|
|
@ -238,6 +238,10 @@ Feature surfaces under `frontend-modern/src/features/` may own product-specific
|
|||
assessment semantics, but they must keep those semantics in their governed
|
||||
presentation helpers and render them inside the shared neutral Pulse surface
|
||||
language rather than introducing page-local verdict bands or nested cards.
|
||||
Feature-owned table drawers use shared disclosure and inline-detail primitives
|
||||
as local interaction state. Unless a surface has a separately governed deep-link
|
||||
write contract, opening or closing a row drawer must preserve the current
|
||||
document and URL instead of writing route state or reloading the page shell.
|
||||
|
||||
Shared filter/search primitives may provide the common shell, keyboard behavior,
|
||||
history, and reset mechanics, but the owning page or table must supply
|
||||
|
|
|
|||
|
|
@ -245,6 +245,13 @@ regression protection.
|
|||
mounted, report errors out of band, and must not rely on Solid
|
||||
`createResource` in a way that can bubble into the route-level Suspense
|
||||
fallback or the setup/welcome shell.
|
||||
Workload row drawer expansion, local row focus, and summary group pinning
|
||||
are local interaction state. Inbound Workloads deep links may hydrate
|
||||
`resource` and `summaryGroup` into the focused row or group, but opening,
|
||||
closing, or clearing an inline drawer must not write those params back to
|
||||
route state, schedule router navigation, or trip the app-shell fallback.
|
||||
URL synchronization for Workloads stays limited to filter-owned scope in
|
||||
`frontend-modern/src/components/Workloads/useWorkloadUrlSync.ts`.
|
||||
The Workloads page may show a bounded partial-inventory banner when a
|
||||
configured workload-capable source is unauthorized, unreachable, stale,
|
||||
pending, or paused, but it must not fabricate VM/container rows from host
|
||||
|
|
|
|||
|
|
@ -196,6 +196,12 @@ Swarm capability surfaces.
|
|||
accessible group label, while Proxmox, Kubernetes, and other platform
|
||||
clusters stay reserved for actual resource identity, filters, and detail
|
||||
surfaces.
|
||||
Infrastructure row drawer expansion and summary group focus are local table
|
||||
interaction state. Inbound infrastructure deep links may hydrate `resource`
|
||||
and `summaryGroup` into the focused resource or group, but local expansion,
|
||||
closing, and clearing must not rewrite those params or navigate. Route sync
|
||||
for this surface is limited to filter-owned `source` and `q` state so
|
||||
unified-resource rows open inline without refreshing the page shell.
|
||||
Cluster group headers may use a compact `Cluster` type chip when the group
|
||||
name itself is only an estate label, but `unifiedResourceTableStateModel.ts`
|
||||
must suppress that chip when the visible group name already ends in
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|||
|
||||
import type { SummarySeriesGroupScope } from '@/components/shared/summaryCardInteraction';
|
||||
import type { WorkloadGuest } from '@/types/workloads';
|
||||
import { ROUTE_STATE_REPLACE_OPTIONS } from '@/utils/routeStateNavigation';
|
||||
|
||||
import { resolveWorkloadResourceSelection } from '../workloadSelectionModel';
|
||||
import { useWorkloadSelectionState } from '../useWorkloadSelectionState';
|
||||
|
|
@ -101,7 +100,7 @@ describe('useWorkloadSelectionState', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('writes workload row selection back into the route state without dropping filters', () => {
|
||||
it('opens workload row selection locally without route navigation', () => {
|
||||
locationSearch = '?type=app-container&platform=truenas&agent=truenas-main';
|
||||
const [filteredGuests] = createSignal<WorkloadGuest[]>([]);
|
||||
|
||||
|
|
@ -115,13 +114,11 @@ describe('useWorkloadSelectionState', () => {
|
|||
result.setSelectedGuestId('app-container:truenas-main:nextcloud');
|
||||
vi.runAllTimers();
|
||||
|
||||
expect(navigateSpy).toHaveBeenCalledWith(
|
||||
'/workloads?type=app-container&platform=truenas&agent=truenas-main&resource=app-container%3Atruenas-main%3Anextcloud',
|
||||
ROUTE_STATE_REPLACE_OPTIONS,
|
||||
);
|
||||
expect(result.selectedGuestId()).toBe('app-container:truenas-main:nextcloud');
|
||||
expect(navigateSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('clears pinned workload scope back to page state without mutating route filters', () => {
|
||||
it('clears pinned workload scope locally without mutating route filters', () => {
|
||||
locationSearch =
|
||||
'?type=app-container&platform=truenas&agent=truenas-main&resource=app-container%3Atruenas-main%3Anextcloud&summaryGroup=docker-host%3Atruenas-main';
|
||||
const [filteredGuests] = createSignal<WorkloadGuest[]>([]);
|
||||
|
|
@ -147,10 +144,9 @@ describe('useWorkloadSelectionState', () => {
|
|||
result.clearPinnedSummaryScope();
|
||||
vi.runAllTimers();
|
||||
|
||||
expect(navigateSpy).toHaveBeenCalledWith(
|
||||
'/workloads?type=app-container&platform=truenas&agent=truenas-main',
|
||||
ROUTE_STATE_REPLACE_OPTIONS,
|
||||
);
|
||||
expect(result.selectedGuestId()).toBeNull();
|
||||
expect(result.focusedSummaryWorkloadGroupId()).toBeNull();
|
||||
expect(navigateSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('routes Escape through workload scope clearing and additional page reset work', () => {
|
||||
|
|
@ -171,10 +167,7 @@ describe('useWorkloadSelectionState', () => {
|
|||
vi.runAllTimers();
|
||||
|
||||
expect(clearAdditionalPageStateOnEscape).toHaveBeenCalledTimes(1);
|
||||
expect(navigateSpy).toHaveBeenCalledWith(
|
||||
'/workloads?type=app-container&platform=truenas&agent=truenas-main',
|
||||
ROUTE_STATE_REPLACE_OPTIONS,
|
||||
);
|
||||
expect(navigateSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('preserves the nearest scrollable ancestor when row focus changes locally', () => {
|
||||
|
|
@ -211,10 +204,8 @@ describe('useWorkloadSelectionState', () => {
|
|||
|
||||
vi.runAllTimers();
|
||||
|
||||
expect(navigateSpy).toHaveBeenCalledWith(
|
||||
'/workloads?type=app-container&platform=truenas&agent=truenas-main&resource=app-container%3Atruenas-main%3Anextcloud',
|
||||
ROUTE_STATE_REPLACE_OPTIONS,
|
||||
);
|
||||
expect(result.selectedGuestId()).toBe('app-container:truenas-main:nextcloud');
|
||||
expect(navigateSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('shows a deliberate jump affordance when a hovered workload row is off-screen', () => {
|
||||
|
|
@ -436,7 +427,7 @@ describe('useWorkloadSelectionState', () => {
|
|||
expect(result.focusedSummaryWorkloadGroupId()).toBe('cluster-a');
|
||||
});
|
||||
|
||||
it('clears row focus without leaving behind an inferred agent filter', () => {
|
||||
it('clears row focus locally without leaving behind an inferred agent filter', () => {
|
||||
locationSearch = '?resource=cluster-a:node-1:101';
|
||||
const [filteredGuests] = createSignal<WorkloadGuest[]>([]);
|
||||
|
||||
|
|
@ -452,6 +443,7 @@ describe('useWorkloadSelectionState', () => {
|
|||
result.setSelectedGuestId(null);
|
||||
vi.runAllTimers();
|
||||
|
||||
expect(navigateSpy).toHaveBeenCalledWith('/workloads', ROUTE_STATE_REPLACE_OPTIONS);
|
||||
expect(result.selectedGuestId()).toBeNull();
|
||||
expect(navigateSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import type { WorkloadGuest } from '@/types/workloads';
|
|||
import {
|
||||
workloadsHasHoveredWorkload,
|
||||
resolveWorkloadResourceSelection,
|
||||
resolveWorkloadsSelectionNavigateTarget,
|
||||
} from '../workloadSelectionModel';
|
||||
|
||||
describe('workloadSelectionModel', () => {
|
||||
|
|
@ -51,57 +50,10 @@ describe('workloadSelectionModel', () => {
|
|||
expect(workloadsHasHoveredWorkload(guests, 'cluster-a:node-1:102')).toBe(false);
|
||||
});
|
||||
|
||||
it('builds route-backed workload selection targets without dropping other filters', () => {
|
||||
expect(
|
||||
resolveWorkloadsSelectionNavigateTarget({
|
||||
pathname: '/workloads',
|
||||
search: '?type=app-container&platform=truenas&agent=truenas-main',
|
||||
resourceId: 'app-container:truenas-main:nextcloud',
|
||||
summaryGroupId: null,
|
||||
}),
|
||||
).toBe(
|
||||
'/workloads?type=app-container&platform=truenas&agent=truenas-main&resource=app-container%3Atruenas-main%3Anextcloud',
|
||||
);
|
||||
|
||||
expect(
|
||||
resolveWorkloadsSelectionNavigateTarget({
|
||||
pathname: '/workloads',
|
||||
search:
|
||||
'?type=app-container&platform=truenas&agent=truenas-main&resource=app-container%3Atruenas-main%3Anextcloud',
|
||||
resourceId: null,
|
||||
summaryGroupId: null,
|
||||
}),
|
||||
).toBe('/workloads?type=app-container&platform=truenas&agent=truenas-main');
|
||||
|
||||
expect(
|
||||
resolveWorkloadsSelectionNavigateTarget({
|
||||
pathname: '/workloads',
|
||||
search:
|
||||
'?type=app-container&platform=truenas&agent=truenas-main&resource=app-container%3Atruenas-main%3Anextcloud',
|
||||
resourceId: 'app-container:truenas-main:nextcloud',
|
||||
summaryGroupId: null,
|
||||
}),
|
||||
).toBeNull();
|
||||
|
||||
expect(
|
||||
resolveWorkloadsSelectionNavigateTarget({
|
||||
pathname: '/workloads',
|
||||
search: '?type=app-container&platform=truenas&agent=truenas-main',
|
||||
resourceId: null,
|
||||
summaryGroupId: 'docker-host:truenas-main',
|
||||
}),
|
||||
).toBe(
|
||||
'/workloads?type=app-container&platform=truenas&agent=truenas-main&summaryGroup=docker-host%3Atruenas-main',
|
||||
);
|
||||
|
||||
expect(
|
||||
resolveWorkloadsSelectionNavigateTarget({
|
||||
pathname: '/workloads',
|
||||
search:
|
||||
'?type=app-container&platform=truenas&agent=truenas-main&summaryGroup=docker-host%3Atruenas-main',
|
||||
resourceId: null,
|
||||
summaryGroupId: 'docker-host:truenas-main',
|
||||
}),
|
||||
).toBeNull();
|
||||
it('resolves summary group deep links separately from local row expansion', () => {
|
||||
expect(resolveWorkloadResourceSelection('?summaryGroup=docker-host%3Atruenas-main')).toEqual({
|
||||
resourceId: null,
|
||||
summaryGroupId: 'docker-host:truenas-main',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useLocation, useNavigate } from '@solidjs/router';
|
||||
import { createEffect, createMemo, createSignal, onCleanup, untrack, type Accessor } from 'solid-js';
|
||||
import { useLocation } from '@solidjs/router';
|
||||
import { createEffect, createMemo, createSignal, type Accessor } from 'solid-js';
|
||||
|
||||
import { preserveScrollableAncestorVerticalOffset } from '@/components/shared/contextualFocus';
|
||||
import { useSummaryPageInteractionState } from '@/components/shared/summaryTableFocus';
|
||||
|
|
@ -9,12 +9,10 @@ import {
|
|||
} from '@/components/shared/summaryCardInteraction';
|
||||
import type { WorkloadGuest } from '@/types/workloads';
|
||||
import { capturePendingAppShellRestoreTop } from '@/utils/appShellScrollRestoration';
|
||||
import { createRouteStateNavigateScheduler } from '@/utils/routeStateNavigation';
|
||||
import {
|
||||
workloadsHasHoveredWorkload,
|
||||
workloadsHasVisibleWorkloadGroupScope,
|
||||
resolveWorkloadResourceSelection,
|
||||
resolveWorkloadsSelectionNavigateTarget,
|
||||
} from './workloadSelectionModel';
|
||||
|
||||
interface UseWorkloadsSelectionStateOptions {
|
||||
|
|
@ -25,11 +23,6 @@ interface UseWorkloadsSelectionStateOptions {
|
|||
|
||||
export function useWorkloadSelectionState(options: UseWorkloadsSelectionStateOptions) {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const routeStateNavigate = createRouteStateNavigateScheduler(
|
||||
navigate,
|
||||
() => `${untrack(() => location.pathname)}${untrack(() => location.search)}`,
|
||||
);
|
||||
|
||||
const [selectedGuestId, setSelectedGuestIdRaw] = createSignal<string | null>(null);
|
||||
const [selectedWorkloadGroupId, setSelectedWorkloadGroupIdRaw] = createSignal<string | null>(null);
|
||||
|
|
@ -57,15 +50,6 @@ export function useWorkloadSelectionState(options: UseWorkloadsSelectionStateOpt
|
|||
setSelectedGuestIdRaw(null);
|
||||
setSelectedWorkloadGroupIdRaw(null);
|
||||
});
|
||||
const nextPath = resolveWorkloadsSelectionNavigateTarget({
|
||||
pathname: location.pathname,
|
||||
search: location.search,
|
||||
resourceId: null,
|
||||
summaryGroupId: null,
|
||||
});
|
||||
if (nextPath) {
|
||||
routeStateNavigate.schedule(nextPath);
|
||||
}
|
||||
};
|
||||
|
||||
const summaryInteraction = useSummaryPageInteractionState({
|
||||
|
|
@ -111,15 +95,6 @@ export function useWorkloadSelectionState(options: UseWorkloadsSelectionStateOpt
|
|||
setSelectedGuestIdRaw(id);
|
||||
setSelectedWorkloadGroupIdRaw(nextGroupScope?.id ?? null);
|
||||
});
|
||||
const nextPath = resolveWorkloadsSelectionNavigateTarget({
|
||||
pathname: location.pathname,
|
||||
search: location.search,
|
||||
resourceId: id,
|
||||
summaryGroupId: nextGroupScope?.id ?? null,
|
||||
});
|
||||
if (nextPath) {
|
||||
routeStateNavigate.schedule(nextPath);
|
||||
}
|
||||
};
|
||||
|
||||
const setFocusedWorkloadGroupScopeState = (scope: SummarySeriesGroupScope | null) => {
|
||||
|
|
@ -132,19 +107,7 @@ export function useWorkloadSelectionState(options: UseWorkloadsSelectionStateOpt
|
|||
};
|
||||
|
||||
const setFocusedWorkloadGroupScope = (scope: SummarySeriesGroupScope | null) => {
|
||||
const nextGroupId = scope?.id ?? null;
|
||||
const nextSelectedGuestId =
|
||||
scope && !isSummarySeriesInGroupScope(scope, selectedGuestId()) ? null : selectedGuestId();
|
||||
setFocusedWorkloadGroupScopeState(scope);
|
||||
const nextPath = resolveWorkloadsSelectionNavigateTarget({
|
||||
pathname: location.pathname,
|
||||
search: location.search,
|
||||
resourceId: nextSelectedGuestId,
|
||||
summaryGroupId: nextGroupId,
|
||||
});
|
||||
if (nextPath) {
|
||||
routeStateNavigate.schedule(nextPath);
|
||||
}
|
||||
};
|
||||
|
||||
createEffect(() => {
|
||||
|
|
@ -219,10 +182,6 @@ export function useWorkloadSelectionState(options: UseWorkloadsSelectionStateOpt
|
|||
}
|
||||
});
|
||||
|
||||
onCleanup(() => {
|
||||
routeStateNavigate.cleanup();
|
||||
});
|
||||
|
||||
return {
|
||||
activeSummaryScopeState: summaryInteraction.activeScopeState,
|
||||
activeSummaryWorkloadGroupScope: summaryInteraction.activeGroupScope,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
import {
|
||||
parseWorkloadsLinkSearch,
|
||||
WORKLOADS_PATH,
|
||||
WORKLOADS_QUERY_PARAMS,
|
||||
} from '@/routing/resourceLinks';
|
||||
import type { SummarySeriesGroupScope } from '@/components/shared/summaryCardInteraction';
|
||||
import type { WorkloadGuest } from '@/types/workloads';
|
||||
import { areSearchParamsEquivalent } from '@/utils/searchParams';
|
||||
import { getCanonicalWorkloadId } from '@/utils/workloads';
|
||||
|
||||
export interface WorkloadsResourceSelection {
|
||||
|
|
@ -13,13 +10,6 @@ export interface WorkloadsResourceSelection {
|
|||
summaryGroupId: string | null;
|
||||
}
|
||||
|
||||
export interface WorkloadsSelectionNavigateTargetOptions {
|
||||
pathname: string;
|
||||
search: string;
|
||||
resourceId: string | null;
|
||||
summaryGroupId: string | null;
|
||||
}
|
||||
|
||||
export const resolveWorkloadResourceSelection = (
|
||||
search: string,
|
||||
): WorkloadsResourceSelection | null => {
|
||||
|
|
@ -32,35 +22,6 @@ export const resolveWorkloadResourceSelection = (
|
|||
};
|
||||
};
|
||||
|
||||
export const resolveWorkloadsSelectionNavigateTarget = ({
|
||||
pathname,
|
||||
search,
|
||||
resourceId,
|
||||
summaryGroupId,
|
||||
}: WorkloadsSelectionNavigateTargetOptions): string | null => {
|
||||
const currentParams = new URLSearchParams(search);
|
||||
const nextParams = new URLSearchParams(search);
|
||||
nextParams.delete(WORKLOADS_QUERY_PARAMS.resource);
|
||||
nextParams.delete(WORKLOADS_QUERY_PARAMS.summaryGroup);
|
||||
|
||||
const normalizedResourceId = resourceId?.trim() || '';
|
||||
const normalizedSummaryGroupId = summaryGroupId?.trim() || '';
|
||||
if (normalizedResourceId) {
|
||||
nextParams.set(WORKLOADS_QUERY_PARAMS.resource, normalizedResourceId);
|
||||
}
|
||||
if (normalizedSummaryGroupId) {
|
||||
nextParams.set(WORKLOADS_QUERY_PARAMS.summaryGroup, normalizedSummaryGroupId);
|
||||
}
|
||||
|
||||
if (areSearchParamsEquivalent(currentParams, nextParams)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const nextSearch = nextParams.toString();
|
||||
const nextPathname = pathname.trim() || WORKLOADS_PATH;
|
||||
return nextSearch ? `${nextPathname}?${nextSearch}` : nextPathname;
|
||||
};
|
||||
|
||||
export const workloadsHasHoveredWorkload = (
|
||||
filteredGuests: WorkloadGuest[],
|
||||
hoveredId: string,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
import { renderHook } from '@solidjs/testing-library';
|
||||
import { createSignal } from 'solid-js';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import type { Resource } from '@/types/resource';
|
||||
import { ROUTE_STATE_REPLACE_OPTIONS } from '@/utils/routeStateNavigation';
|
||||
|
||||
import { useInfrastructurePageRouteState } from '../useInfrastructurePageRouteState';
|
||||
|
||||
let locationPath = '/infrastructure';
|
||||
let locationSearch = '';
|
||||
const navigateSpy = vi.fn();
|
||||
|
||||
vi.mock('@solidjs/router', () => ({
|
||||
useLocation: () => ({
|
||||
get pathname() {
|
||||
return locationPath;
|
||||
},
|
||||
get search() {
|
||||
return locationSearch;
|
||||
},
|
||||
}),
|
||||
useNavigate: () => navigateSpy,
|
||||
}));
|
||||
|
||||
const makeResource = (id: string): Resource =>
|
||||
({
|
||||
id,
|
||||
name: id,
|
||||
displayName: id,
|
||||
sourceType: 'agent',
|
||||
status: 'online',
|
||||
type: 'agent',
|
||||
}) as Resource;
|
||||
|
||||
describe('useInfrastructurePageRouteState', () => {
|
||||
beforeEach(() => {
|
||||
locationPath = '/infrastructure';
|
||||
locationSearch = '';
|
||||
navigateSpy.mockReset();
|
||||
vi.useFakeTimers();
|
||||
vi.stubGlobal('requestAnimationFrame', (callback: FrameRequestCallback) => {
|
||||
callback(0);
|
||||
return 1;
|
||||
});
|
||||
vi.stubGlobal('cancelAnimationFrame', vi.fn());
|
||||
Object.defineProperty(window.history, 'scrollRestoration', {
|
||||
configurable: true,
|
||||
value: 'auto',
|
||||
writable: true,
|
||||
});
|
||||
window.scrollTo = vi.fn() as typeof window.scrollTo;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.runOnlyPendingTimers();
|
||||
vi.useRealTimers();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
const setup = () => {
|
||||
const [resources] = createSignal<Resource[]>([makeResource('agent-1'), makeResource('agent-2')]);
|
||||
const [selectedSource, setSelectedSource] = createSignal('');
|
||||
const [searchQuery, setSearchQuery] = createSignal('');
|
||||
const [initialLoadComplete] = createSignal(true);
|
||||
|
||||
const rendered = renderHook(() =>
|
||||
useInfrastructurePageRouteState({
|
||||
resources,
|
||||
filteredResources: resources,
|
||||
initialLoadComplete,
|
||||
selectedSource,
|
||||
setSelectedSource,
|
||||
searchQuery,
|
||||
setSearchQuery,
|
||||
}),
|
||||
);
|
||||
|
||||
return { ...rendered, setSelectedSource, setSearchQuery };
|
||||
};
|
||||
|
||||
it('hydrates an inbound resource deep link into drawer state', () => {
|
||||
locationSearch = '?resource=agent-1';
|
||||
|
||||
const { result } = setup();
|
||||
|
||||
expect(result.expandedResourceId()).toBe('agent-1');
|
||||
expect(navigateSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('keeps local drawer expansion off the route navigation path', () => {
|
||||
const { result } = setup();
|
||||
|
||||
result.setExpandedResourceId('agent-2');
|
||||
vi.runAllTimers();
|
||||
|
||||
expect(result.expandedResourceId()).toBe('agent-2');
|
||||
expect(navigateSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('keeps infrastructure filters route-backed', () => {
|
||||
const { setSelectedSource } = setup();
|
||||
|
||||
setSelectedSource('docker');
|
||||
vi.runAllTimers();
|
||||
|
||||
expect(navigateSpy).toHaveBeenCalledWith(
|
||||
'/infrastructure?source=docker',
|
||||
ROUTE_STATE_REPLACE_OPTIONS,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -145,28 +145,16 @@ export function useInfrastructurePageRouteState(options: InfrastructurePageRoute
|
|||
|
||||
const nextSource = selectedSource();
|
||||
const nextQuery = searchQuery().trim();
|
||||
const currentLinkedResource = parsed.resource;
|
||||
const selectedResourceId = expandedResourceId();
|
||||
const shouldPreserveIncomingResource =
|
||||
!selectedResourceId && Boolean(currentLinkedResource) && !initialLoadComplete();
|
||||
const nextResource = shouldPreserveIncomingResource
|
||||
? currentLinkedResource
|
||||
: (selectedResourceId ?? '');
|
||||
const nextSummaryGroup = focusedResourceGroupId() ?? '';
|
||||
|
||||
const managedPath = buildInfrastructurePath({
|
||||
source: nextSource || null,
|
||||
query: nextQuery || null,
|
||||
resource: nextResource || null,
|
||||
summaryGroup: nextSummaryGroup || null,
|
||||
});
|
||||
const managedUrl = new URL(managedPath, 'http://pulse.local');
|
||||
const currentParams = new URLSearchParams(location.search);
|
||||
const nextParams = new URLSearchParams(location.search);
|
||||
nextParams.delete(INFRASTRUCTURE_QUERY_PARAMS.source);
|
||||
nextParams.delete(INFRASTRUCTURE_QUERY_PARAMS.query);
|
||||
nextParams.delete(INFRASTRUCTURE_QUERY_PARAMS.resource);
|
||||
nextParams.delete(INFRASTRUCTURE_QUERY_PARAMS.summaryGroup);
|
||||
managedUrl.searchParams.forEach((value, key) => {
|
||||
nextParams.set(key, value);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -762,7 +762,7 @@ describe('frontend resource type boundaries', () => {
|
|||
expect(workloadsWorkloadRouteStateSource).toContain('useWorkloadFilterOptions');
|
||||
expect(workloadsWorkloadFilterOptionsSource).toContain("from './workloadFilterConfigModel'");
|
||||
expect(workloadsWorkloadFilterOptionsSource).toContain(
|
||||
'buildWorkloadNodeOptions(options.allGuests())',
|
||||
'buildWorkloadNodeOptions(platformScopedGuests())',
|
||||
);
|
||||
expect(workloadsWorkloadFilterOptionsSource).not.toContain(
|
||||
'const onContextChange = (value: string) =>',
|
||||
|
|
@ -1018,7 +1018,7 @@ describe('frontend resource type boundaries', () => {
|
|||
expect(guestDrawerSource).toContain('useGuestDrawerState');
|
||||
expect(guestDrawerSource).toContain('GuestDrawerOverview');
|
||||
expect(guestDrawerStateSource).toContain('getCanonicalWorkloadId');
|
||||
expect(guestDrawerStateSource).toContain("from '@/routing/resourceLinks'");
|
||||
expect(guestDrawerStateSource).not.toContain("from '@/routing/resourceLinks'");
|
||||
expect(guestDrawerStateSource).not.toContain('./infrastructureLink');
|
||||
expect(guestDrawerStateSource).toContain('guestOsSummary');
|
||||
expect(guestDrawerSource).not.toContain('const guestId = () => {');
|
||||
|
|
@ -2702,7 +2702,7 @@ describe('frontend resource type boundaries', () => {
|
|||
expect(commandPaletteModalSource).not.toContain('buildInfrastructurePath');
|
||||
expect(commandPaletteStateSource).toContain('useNavigate');
|
||||
expect(commandPaletteStateSource).toContain('createSignal');
|
||||
expect(commandPaletteStateSource).toContain('buildInfrastructurePath');
|
||||
expect(commandPaletteStateSource).not.toContain('buildInfrastructurePath');
|
||||
expect(commandPaletteModelSource).toContain('buildCommandPaletteCommands');
|
||||
expect(commandPaletteModelSource).toContain('normalizeCommandPaletteQuery');
|
||||
expect(commandPaletteModelSource).toContain('filterCommandPaletteCommands');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue