From fee685a120419ccc453da723d07324754237f1e9 Mon Sep 17 00:00:00 2001 From: Wouter Admiraal Date: Tue, 13 Jun 2023 12:47:04 +0200 Subject: [PATCH] SONAR-19437 Browser fails to display previous page --- .../app/components/ProjectAdminContainer.tsx | 35 +++++++++++++++---- .../__tests__/ProjectAdminContainer-test.tsx | 3 ++ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/server/sonar-web/src/main/js/app/components/ProjectAdminContainer.tsx b/server/sonar-web/src/main/js/app/components/ProjectAdminContainer.tsx index 5d1ded1dc5b..25eeb223b68 100644 --- a/server/sonar-web/src/main/js/app/components/ProjectAdminContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/ProjectAdminContainer.tsx @@ -29,24 +29,47 @@ interface Props { } export class ProjectAdminContainer extends React.PureComponent { + mounted = false; + componentDidMount() { - this.checkPermissions(); + // We need to check permissions *after* the parent ComponentContainer is updated. + // This is why we wrap checkPermission() in a setTimeout(). + // + // We want to prevent an edge-case where if you navigate from an admin component page + // to another component page where you have "read" access but no admin access, and + // then hit the browser's Back button, you will get redirected to the login page. + // + // The reason is that this component will get mounted *before* the parent + // ComponentContainer is *updated*. The parent component still has the last component + // stored in state, the one where the user might not have admin access. It will detect + // the change in URL and trigger a refresh of its state, but this comes too late; the + // checkPermission() here will already have triggered (because mounts happen before updates) + // and redirected the user. Wrapping it in a setTimeout() allows the parent component + // to refresh, unmounting this component, and only triggering the redirect if it makes sense. + // + // See https://sonarsource.atlassian.net/browse/SONAR-19437 + setTimeout(this.checkPermissions, 0); + this.mounted = true; } componentDidUpdate() { this.checkPermissions(); } - checkPermissions() { - if (!this.isProjectAdmin()) { + componentWillUnmount() { + this.mounted = false; + } + + checkPermissions = () => { + if (this.mounted && !this.isProjectAdmin()) { handleRequiredAuthorization(); } - } + }; - isProjectAdmin() { + isProjectAdmin = () => { const { configuration } = this.props.component; return configuration != null && configuration.showSettings; - } + }; render() { if (!this.isProjectAdmin()) { diff --git a/server/sonar-web/src/main/js/app/components/__tests__/ProjectAdminContainer-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/ProjectAdminContainer-test.tsx index 75fdefb7f3c..d3ed20ab64f 100644 --- a/server/sonar-web/src/main/js/app/components/__tests__/ProjectAdminContainer-test.tsx +++ b/server/sonar-web/src/main/js/app/components/__tests__/ProjectAdminContainer-test.tsx @@ -32,8 +32,11 @@ it('should render correctly', () => { }); it('should redirect for authorization if needed', () => { + jest.useFakeTimers(); mountRender({ component: mockComponent({ configuration: { showSettings: false } }) }); + jest.runAllTimers(); expect(handleRequiredAuthorization).toHaveBeenCalled(); + jest.useRealTimers(); }); function mountRender(props: Partial = {}) { -- 2.39.5