Skip to content

Commit 6ef3045

Browse files
[frontend] feat(multi-tenant): strip detail segments on tenant switch (#4864) (#5589)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1 parent 037193a commit 6ef3045

4 files changed

Lines changed: 46 additions & 11 deletions

File tree

openaev-front/src/__tests__/utils/hooks/UseTenant.test.tsx

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,6 @@ describe('useTenant', () => {
232232
expect(mockBuildTenantUrl).toHaveBeenCalledWith(
233233
TENANT_ALPHA.tenant_id,
234234
expect.any(String),
235-
expect.any(String),
236-
expect.any(String),
237235
);
238236
});
239237
expect(window.location.href).toContain(TENANT_ALPHA.tenant_id);
@@ -253,8 +251,6 @@ describe('useTenant', () => {
253251
expect(mockBuildTenantUrl).toHaveBeenCalledWith(
254252
TENANT_ALPHA.tenant_id,
255253
expect.any(String),
256-
expect.any(String),
257-
expect.any(String),
258254
);
259255
});
260256
expect(window.location.href).toContain(TENANT_ALPHA.tenant_id);
@@ -311,8 +307,6 @@ describe('useTenant', () => {
311307
expect(mockBuildTenantUrl).toHaveBeenCalledWith(
312308
TENANT_BETA.tenant_id,
313309
expect.any(String),
314-
expect.any(String),
315-
expect.any(String),
316310
);
317311
expect(window.location.href).toContain(TENANT_BETA.tenant_id);
318312
});
@@ -418,8 +412,6 @@ describe('useTenant', () => {
418412
expect(mockBuildTenantUrl).toHaveBeenCalledWith(
419413
TENANT_GAMMA.tenant_id,
420414
expect.any(String),
421-
expect.any(String),
422-
expect.any(String),
423415
);
424416
expect(window.location.href).toContain(TENANT_GAMMA.tenant_id);
425417
});

openaev-front/src/__tests__/utils/url-helper.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,28 @@ describe('url-helper', () => {
425425
});
426426
});
427427

428+
// -- stripDetailSegments --
429+
430+
describe('stripDetailSegments', () => {
431+
it.each([
432+
['/admin/scenarios/123e4567-e89b-12d3-a456-426614174000', '/admin/scenarios'],
433+
['/admin/scenarios/123e4567-e89b-12d3-a456-426614174000/injects', '/admin/scenarios'],
434+
['/admin/scenarios/123e4567-e89b-12d3-a456-426614174000/injects/ABCDEF12-0000-1111-2222-333344445555', '/admin/scenarios'],
435+
['/admin/scenarios', '/admin/scenarios'],
436+
['/admin', '/admin'],
437+
['/', '/'],
438+
])('given_%s_should_return_%s', async (input, expected) => {
439+
// Arrange
440+
const { stripDetailSegments } = await importHelper();
441+
442+
// Act
443+
const result = stripDetailSegments(input);
444+
445+
// Assert
446+
expect(result).toBe(expected);
447+
});
448+
});
449+
428450
// -- buildTenantApiPath --
429451

430452
describe('buildTenantApiPath', () => {

openaev-front/src/utils/hooks/useTenant.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { fetchUserTenants } from '../../actions/user/user-tenant-actions';
55
import { TENANT_SWITCH_SUCCESS } from '../../constants/ActionTypes';
66
import { type TenantOutput, type User } from '../api-types';
77
import { useAppDispatch } from '../hooks';
8-
import { buildTenantUrl, extractTenantFromUrl } from '../url-helper';
8+
import { buildTenantUrl, extractTenantFromUrl, stripDetailSegments } from '../url-helper';
99

1010
/**
1111
* Internal hook that encapsulates the current-tenant state and
@@ -57,9 +57,12 @@ const useTenant = (me: User | undefined, logged: unknown, isPlatformRoute: boole
5757
const target = tenants.find(t => t.tenant_id === tenantId);
5858
if (!target) return false;
5959
if (extractTenantFromUrl() !== target.tenant_id) {
60+
// Switching to a different tenant — strip detail segments so we land on
61+
// the list page instead of a detail page for a resource that may not exist.
62+
const safePath = stripDetailSegments(location.pathname);
6063
// Full page navigation — the reload will re-initialise tenant state,
6164
// so we intentionally skip setTenant to avoid a broken intermediate render.
62-
window.location.href = buildTenantUrl(target.tenant_id, location.pathname, location.search, location.hash);
65+
window.location.href = buildTenantUrl(target.tenant_id, safePath);
6366
} else {
6467
setTenant(target);
6568
}

openaev-front/src/utils/url-helper.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,25 @@ export const TENANT_URI = '/api/tenants';
2727
*/
2828
export const DEFAULT_TENANT_UUID = '2cffad3a-0001-4078-b0e2-ef74274022c3';
2929

30-
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
30+
export const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
31+
32+
/**
33+
* Strips entity-specific detail segments from a path so that a tenant switch
34+
* lands on the parent list page rather than a detail page for a resource
35+
* that may not exist in the target tenant.
36+
*
37+
* e.g. "/admin/scenarios/123e4567-e89b-12d3-a456-426614174000"
38+
* → "/admin/scenarios"
39+
* "/admin/scenarios/123e4567-e89b-12d3-a456-426614174000/injects"
40+
* → "/admin/scenarios"
41+
* "/admin/scenarios" → "/admin/scenarios" (unchanged)
42+
*/
43+
export const stripDetailSegments = (pathname: string): string => {
44+
const segments = pathname.split('/').filter(Boolean);
45+
const uuidIndex = segments.findIndex(s => UUID_REGEX.test(s));
46+
if (uuidIndex === -1) return pathname;
47+
return '/' + segments.slice(0, uuidIndex).join('/');
48+
};
3149

3250
// ---------------------------------------------------------------------------
3351
// URL helpers

0 commit comments

Comments
 (0)