From 390fd608860758335abcf2437dd7a9a913133056 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura <35282393+DarshitChanpura@users.noreply.github.com> Date: Wed, 19 Jul 2023 08:22:55 -0400 Subject: [PATCH] [Backport 1.3] Switch to new tenant after loading a copied long URL (#1450) (#1517) * Switch to new tenant after loading a copied long URL (#1450) Signed-off-by: leanneeliatra Signed-off-by: Darshit Chanpura Signed-off-by: Darshit Chanpura <35282393+DarshitChanpura@users.noreply.github.com> Co-authored-by: leanneeliatra <131779422+leanneeliatra@users.noreply.github.com> --- public/apps/account/account-nav-button.tsx | 23 +++++- .../account/test/account-nav-button.test.tsx | 59 ++++++++++++++- .../apps/account/test/switch-tenants.test.tsx | 73 +++++++++++++++++++ 3 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 public/apps/account/test/switch-tenants.test.tsx diff --git a/public/apps/account/account-nav-button.tsx b/public/apps/account/account-nav-button.tsx index 7bd0e578b..09322c995 100644 --- a/public/apps/account/account-nav-button.tsx +++ b/public/apps/account/account-nav-button.tsx @@ -59,7 +59,7 @@ export function AccountNavButton(props: { }} handleSwitchAndClose={() => { setModal(null); - window.location.reload(); + reloadAfterTenantSwitch(); }} /> ), @@ -164,3 +164,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 14fa038ff..ca8f8d98a 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'; jest.mock('../../../utils/storage-utils', () => ({ @@ -102,3 +102,60 @@ describe('Account navigation button', () => { expect(setState).toBeCalledTimes(1); }); }); + +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 000000000..1eb798fe0 --- /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); + }); +});