From 337ba04ed0aeb49728775657ebcfad739109548d Mon Sep 17 00:00:00 2001 From: Philippe Perrin Date: Mon, 8 Jun 2020 13:35:26 +0200 Subject: [PATCH] SONAR-13413 Issues page is not available while indexation is in progress SONAR-13400 Portfolios pages are not available while indexation is in progress SONAR-13398 Project page is not available while indexation is in progress SONAR-13402 Application page is not available while indexation is in progress --- .../js/app/components/ComponentContainer.tsx | 5 ++ .../__tests__/ComponentContainer-test.tsx | 13 +++ .../components/extensions/PortfoliosPage.tsx | 6 +- .../PageUnavailableDueToIndexation.tsx | 79 +++++++++++++++++++ .../PageUnavailableDueToIndexation-test.tsx | 57 +++++++++++++ ...geUnavailableDueToIndexation-test.tsx.snap | 40 ++++++++++ .../src/main/js/app/utils/startReactApp.tsx | 7 +- .../__tests__/withIndexationGuard-test.tsx | 50 ++++++++++++ .../js/components/hoc/withIndexationGuard.tsx | 46 +++++++++++ server/sonar-web/src/main/js/types/types.d.ts | 1 + .../resources/org/sonar/l10n/core.properties | 6 +- 11 files changed, 307 insertions(+), 3 deletions(-) create mode 100644 server/sonar-web/src/main/js/app/components/indexation/PageUnavailableDueToIndexation.tsx create mode 100644 server/sonar-web/src/main/js/app/components/indexation/__tests__/PageUnavailableDueToIndexation-test.tsx create mode 100644 server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/PageUnavailableDueToIndexation-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/hoc/__tests__/withIndexationGuard-test.tsx create mode 100644 server/sonar-web/src/main/js/components/hoc/withIndexationGuard.tsx diff --git a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx index cb82b50cc0b..ecab0962600 100644 --- a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx @@ -42,6 +42,7 @@ import { BranchLike } from '../../types/branch-like'; import { isPortfolioLike } from '../../types/component'; import ComponentContainerNotFound from './ComponentContainerNotFound'; import { ComponentContext } from './ComponentContext'; +import PageUnavailableDueToIndexation from './indexation/PageUnavailableDueToIndexation'; import ComponentNav from './nav/component/ComponentNav'; interface Props { @@ -322,6 +323,10 @@ export class ComponentContainer extends React.PureComponent { return ; } + if (component?.needIssueSync) { + return ; + } + const { branchLike, branchLikes, currentTask, isPending, tasksInProgress } = this.state; const isInProgress = tasksInProgress && tasksInProgress.length > 0; diff --git a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx index a69eb36c59b..6d12970fe6b 100644 --- a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx +++ b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx @@ -29,6 +29,7 @@ import { mockBranch, mockMainBranch, mockPullRequest } from '../../../helpers/mo import { mockComponent, mockLocation, mockRouter } from '../../../helpers/testMocks'; import { ComponentQualifier } from '../../../types/component'; import { ComponentContainer } from '../ComponentContainer'; +import PageUnavailableDueToIndexation from '../indexation/PageUnavailableDueToIndexation'; jest.mock('../../../api/branches', () => { const { mockMainBranch, mockPullRequest } = require.requireActual( @@ -210,6 +211,18 @@ it('should redirect if the component is a portfolio', async () => { expect(replace).toBeCalledWith({ pathname: '/portfolio', query: { id: componentKey } }); }); +it('should display display the unavailable page if the component needs issue sync', async () => { + (getComponentData as jest.Mock).mockResolvedValueOnce({ + component: { key: 'test', qualifier: ComponentQualifier.Project, needIssueSync: true } + }); + + const wrapper = shallowRender(); + + await waitAndUpdate(wrapper); + + expect(wrapper.find(PageUnavailableDueToIndexation).exists()).toBe(true); +}); + function shallowRender(props: Partial = {}) { return shallow( ; } + +export default withIndexationGuard(PortfoliosPage, PageContext.Portfolios); diff --git a/server/sonar-web/src/main/js/app/components/indexation/PageUnavailableDueToIndexation.tsx b/server/sonar-web/src/main/js/app/components/indexation/PageUnavailableDueToIndexation.tsx new file mode 100644 index 00000000000..ef88aa8cef6 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/indexation/PageUnavailableDueToIndexation.tsx @@ -0,0 +1,79 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import * as React from 'react'; +import { FormattedMessage } from 'react-intl'; +import { Alert } from 'sonar-ui-common/components/ui/Alert'; +import { translate } from 'sonar-ui-common/helpers/l10n'; +import withIndexationContext, { + WithIndexationContextProps +} from '../../../components/hoc/withIndexationContext'; + +interface Props extends WithIndexationContextProps { + pageContext?: PageContext; + component?: Pick; +} + +export enum PageContext { + Issues = 'issues', + Portfolios = 'portfolios' +} + +export class PageUnavailableDueToIndexation extends React.PureComponent { + componentDidUpdate() { + if (this.props.indexationContext?.status.isCompleted) { + window.location.reload(); + } + } + + render() { + const { pageContext, component } = this.props; + let messageKey = 'indexation.page_unavailable.title'; + + if (pageContext) { + messageKey = `${messageKey}.${pageContext}`; + } + + return ( +
+
+

+ {component?.name} + }} + /> +

+ +

{translate('indexation.page_unavailable.description')}

+

+ {translate('indexation.page_unavailable.description.additional_information')} +

+
+
+
+ ); + } +} + +export default withIndexationContext(PageUnavailableDueToIndexation); diff --git a/server/sonar-web/src/main/js/app/components/indexation/__tests__/PageUnavailableDueToIndexation-test.tsx b/server/sonar-web/src/main/js/app/components/indexation/__tests__/PageUnavailableDueToIndexation-test.tsx new file mode 100644 index 00000000000..d988c8764dc --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/indexation/__tests__/PageUnavailableDueToIndexation-test.tsx @@ -0,0 +1,57 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { shallow } from 'enzyme'; +import * as React from 'react'; +import { ComponentQualifier } from '../../../../types/component'; +import { PageContext, PageUnavailableDueToIndexation } from '../PageUnavailableDueToIndexation'; + +it('should render correctly', () => { + const wrapper = shallowRender(); + expect(wrapper).toMatchSnapshot(); +}); + +it('should refresh the page once the indexation is complete', () => { + const reload = jest.fn(); + delete window.location; + (window as any).location = { reload }; + + const wrapper = shallowRender(); + + expect(reload).not.toHaveBeenCalled(); + + wrapper.setProps({ indexationContext: { status: { isCompleted: true } } }); + wrapper.update(); + + expect(reload).toHaveBeenCalled(); +}); + +function shallowRender(props?: PageUnavailableDueToIndexation['props']) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/PageUnavailableDueToIndexation-test.tsx.snap b/server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/PageUnavailableDueToIndexation-test.tsx.snap new file mode 100644 index 00000000000..be114de0437 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/PageUnavailableDueToIndexation-test.tsx.snap @@ -0,0 +1,40 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +
+
+

+ + test-portfolio + , + "componentQualifier": "qualifier.VW", + } + } + /> +

+ +

+ indexation.page_unavailable.description +

+

+ indexation.page_unavailable.description.additional_information +

+
+
+
+`; diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx index cc3c004f5e0..091ca9f1457 100644 --- a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx +++ b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx @@ -65,8 +65,10 @@ import systemRoutes from '../../apps/system/routes'; import usersRoutes from '../../apps/users/routes'; import webAPIRoutes from '../../apps/web-api/routes'; import webhooksRoutes from '../../apps/webhooks/routes'; +import withIndexationGuard from '../../components/hoc/withIndexationGuard'; import App from '../components/App'; import GlobalContainer from '../components/GlobalContainer'; +import { PageContext } from '../components/indexation/PageUnavailableDueToIndexation'; import MigrationContainer from '../components/MigrationContainer'; import * as theme from '../theme'; import getStore from './getStore'; @@ -300,7 +302,10 @@ export default function startReactApp( import('../components/extensions/GlobalPageExtension') )} /> - + diff --git a/server/sonar-web/src/main/js/components/hoc/__tests__/withIndexationGuard-test.tsx b/server/sonar-web/src/main/js/components/hoc/__tests__/withIndexationGuard-test.tsx new file mode 100644 index 00000000000..fca47cf1b8d --- /dev/null +++ b/server/sonar-web/src/main/js/components/hoc/__tests__/withIndexationGuard-test.tsx @@ -0,0 +1,50 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { mount } from 'enzyme'; +import * as React from 'react'; +import { IndexationContext } from '../../../app/components/indexation/IndexationContext'; +import { PageContext } from '../../../app/components/indexation/PageUnavailableDueToIndexation'; +import { IndexationContextInterface } from '../../../types/indexation'; +import withIndexationGuard from '../withIndexationGuard'; + +it('should render correctly', () => { + let wrapper = mountRender(); + expect(wrapper.find(TestComponent).exists()).toBe(false); + + wrapper = mountRender({ status: { isCompleted: true } }); + expect(wrapper.find(TestComponent).exists()).toBe(true); +}); + +function mountRender(context?: Partial) { + return mount( + + + + ); +} + +class TestComponent extends React.PureComponent { + render() { + return

TestComponent

; + } +} + +const TestComponentWithGuard = withIndexationGuard(TestComponent, PageContext.Issues); diff --git a/server/sonar-web/src/main/js/components/hoc/withIndexationGuard.tsx b/server/sonar-web/src/main/js/components/hoc/withIndexationGuard.tsx new file mode 100644 index 00000000000..0036b19fdec --- /dev/null +++ b/server/sonar-web/src/main/js/components/hoc/withIndexationGuard.tsx @@ -0,0 +1,46 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import * as React from 'react'; +import { IndexationContext } from '../../app/components/indexation/IndexationContext'; +import PageUnavailableDueToIndexation, { + PageContext +} from '../../app/components/indexation/PageUnavailableDueToIndexation'; + +export default function withIndexationGuard

( + WrappedComponent: React.ComponentType

, + pageContext: PageContext +) { + return class WithIndexationGuard extends React.PureComponent

{ + render() { + return ( + + {context => + context?.status.isCompleted ? ( + + ) : ( + + ) + } + + ); + } + }; +} diff --git a/server/sonar-web/src/main/js/types/types.d.ts b/server/sonar-web/src/main/js/types/types.d.ts index 72b81c469c0..9f92aa7cd85 100644 --- a/server/sonar-web/src/main/js/types/types.d.ts +++ b/server/sonar-web/src/main/js/types/types.d.ts @@ -126,6 +126,7 @@ declare namespace T { isFavorite?: boolean; leakPeriodDate?: string; name: string; + needIssueSync?: boolean; path?: string; refKey?: string; qualityProfiles?: ComponentQualityProfile[]; diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index bd2f2512a13..97bd3af3747 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -3566,7 +3566,11 @@ indexation.in_progress=SonarQube is reloading project data. Some projects will b indexation.in_progress.details={0}% completed. indexation.in_progress.admin_details=See {link}. indexation.completed=All project data has been reloaded. - +indexation.page_unavailable.title.issues=Issues page is temporarily unavailable +indexation.page_unavailable.title.portfolios=Portfolios page is temporarily unavailable +indexation.page_unavailable.title={componentQualifier} {componentName} is temporarily unavailable +indexation.page_unavailable.description=This page will be available after the data is reloaded. This might take a while depending on the amount of projects and issues in your SonarQube instance. +indexation.page_unavailable.description.additional_information=You can keep analyzing your projects during this process. #------------------------------------------------------------------------------ # # HOMEPAGE -- 2.39.5