diff --git a/public/apps/account/account-nav-button.tsx b/public/apps/account/account-nav-button.tsx index 8f67c33286..02e04f3af4 100644 --- a/public/apps/account/account-nav-button.tsx +++ b/public/apps/account/account-nav-button.tsx @@ -62,7 +62,7 @@ export function AccountNavButton(props: { }} handleSwitchAndClose={() => { setModal(null); - window.location.reload(); + reloadAfterTenantSwitch(); }} tenant={props.tenant!} /> @@ -186,3 +186,24 @@ export function AccountNavButton(props: { ); } + +export function reloadAfterTenantSwitch(): void { + // the below portion is to clear URLs starting with 'lastUrl' + // when switching tenants, the last URLs will be from the old tenancy therefore we need to remove these from sessionStorage. + const lastUrls = []; + const { sessionStorage } = window; + + for (let i = 0; i < sessionStorage.length; i++) { + const key = sessionStorage.key(i); + if (key?.startsWith('lastUrl')) { + lastUrls.push(key); + } + } + for (let i = 0; i < lastUrls.length; i++) { + sessionStorage.removeItem(lastUrls[i]); + } + + // rather than just reload when we switch tenants, we set the URL to the pathname. i.e. the portion like: '/app/dashboards' + // therefore, the copied URL will now allow tenancy changes. + window.location.href = window.location.pathname; +} diff --git a/public/apps/account/test/account-nav-button.test.tsx b/public/apps/account/test/account-nav-button.test.tsx index 0bf24798e4..ff20b0f255 100644 --- a/public/apps/account/test/account-nav-button.test.tsx +++ b/public/apps/account/test/account-nav-button.test.tsx @@ -15,7 +15,7 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { AccountNavButton } from '../account-nav-button'; +import { AccountNavButton, reloadAfterTenantSwitch } from '../account-nav-button'; import { getShouldShowTenantPopup, setShouldShowTenantPopup } from '../../../utils/storage-utils'; import { getDashboardsInfo } from '../../../utils/dashboards-info-utils'; @@ -174,3 +174,60 @@ describe('Account navigation button, multitenancy disabled', () => { expect(setState).toBeCalledTimes(0); }); }); + +describe('Reload window after tenant switch', () => { + const originalLocation = window.location; + const mockSetWindowHref = jest.fn(); + let pathname: string = ''; + beforeAll(() => { + pathname = '/app/myapp'; + Object.defineProperty(window, 'location', { + value: { + get pathname() { + return pathname; + }, + get href() { + return '/app/dashboards?security_tenant=admin_tenant'; + }, + set href(value: string) { + mockSetWindowHref(value); + }, + }, + }); + }); + + afterAll(() => { + window.location = originalLocation; + }); + + it('should remove the tenant query parameter before reloading', () => { + pathname = '/app/pathname-only'; + reloadAfterTenantSwitch(); + expect(mockSetWindowHref).toHaveBeenCalledWith(pathname); + }); +}); + +describe('Clear lastUrls after tenant switch', () => { + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should clear out keys with a lastUrl prefix', () => { + window.sessionStorage.setItem('lastUrl:dashboard', '/dashboard1'); + window.sessionStorage.setItem('lastUrl:otherApp', '/otherApp'); + window.sessionStorage.setItem('somethingElse:here', '/random'); + const mockRemoveItem = jest.spyOn(Object.getPrototypeOf(window.sessionStorage), 'removeItem'); + reloadAfterTenantSwitch(); + expect(mockRemoveItem).toHaveBeenCalledWith('lastUrl:dashboard'); + expect(mockRemoveItem).toHaveBeenCalledWith('lastUrl:otherApp'); + expect(mockRemoveItem).toHaveBeenCalledTimes(2); + }); + + it('should not clear out keys without a lastUrl prefix', () => { + window.sessionStorage.setItem('somethingElse:here', '/random'); + const mockRemoveItem = jest.spyOn(Object.getPrototypeOf(window.sessionStorage), 'removeItem'); + + reloadAfterTenantSwitch(); + expect(mockRemoveItem).toHaveBeenCalledTimes(0); + }); +}); diff --git a/public/apps/account/test/switch-tenants.test.tsx b/public/apps/account/test/switch-tenants.test.tsx new file mode 100644 index 0000000000..1eb798fe0e --- /dev/null +++ b/public/apps/account/test/switch-tenants.test.tsx @@ -0,0 +1,73 @@ +/* + * Copyright OpenSearch Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import { reloadAfterTenantSwitch } from '../account-nav-button'; + +describe('Reload window after tenant switch', () => { + const originalLocation = window.location; + const mockSetWindowHref = jest.fn(); + let pathname: string = ''; + beforeAll(() => { + pathname = '/app/myapp'; + Object.defineProperty(window, 'location', { + value: { + get pathname() { + return pathname; + }, + get href() { + return '/app/dashboards?security_tenant=admin_tenant'; + }, + set href(value: string) { + mockSetWindowHref(value); + }, + }, + }); + }); + + afterAll(() => { + window.location = originalLocation; + }); + + it('should remove the tenant query parameter before reloading', () => { + pathname = '/app/pathname-only'; + reloadAfterTenantSwitch(); + expect(mockSetWindowHref).toHaveBeenCalledWith(pathname); + }); +}); + +describe('Clear lastUrls after tenant switch', () => { + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should clear out keys with a lastUrl prefix', () => { + window.sessionStorage.setItem('lastUrl:dashboard', '/dashboard1'); + window.sessionStorage.setItem('lastUrl:otherApp', '/otherApp'); + window.sessionStorage.setItem('somethingElse:here', '/random'); + const mockRemoveItem = jest.spyOn(Object.getPrototypeOf(window.sessionStorage), 'removeItem'); + reloadAfterTenantSwitch(); + expect(mockRemoveItem).toHaveBeenCalledWith('lastUrl:dashboard'); + expect(mockRemoveItem).toHaveBeenCalledWith('lastUrl:otherApp'); + expect(mockRemoveItem).toHaveBeenCalledTimes(2); + }); + + it('should not clear out keys without a lastUrl prefix', () => { + window.sessionStorage.setItem('somethingElse:here', '/random'); + const mockRemoveItem = jest.spyOn(Object.getPrototypeOf(window.sessionStorage), 'removeItem'); + + reloadAfterTenantSwitch(); + expect(mockRemoveItem).toHaveBeenCalledTimes(0); + }); +});